1919#include " source/common/grpc/common.h"
2020#include " source/common/http/header_map_impl.h"
2121#include " source/common/http/header_utility.h"
22+ #include " source/common/http/utility.h"
2223#include " source/common/network/address_impl.h"
2324#include " source/common/network/socket_impl.h"
2425#include " source/common/network/utility.h"
@@ -43,6 +44,23 @@ getMethod(const envoy::config::core::v3::RequestMethod config_method) {
4344 return config_method;
4445}
4546
47+ bool useNegotiatedProtocol () {
48+ return Runtime::runtimeFeatureEnabled (
49+ " envoy.reloadable_features.health_check_use_negotiated_protocol" );
50+ }
51+
52+ // Maps the protocol negotiated by ALPN to a codec type, falling back to `default_codec_type` when
53+ // nothing was negotiated or the negotiated protocol is not one this health checker can speak.
54+ Http::CodecType codecTypeFromAlpn (absl::string_view alpn, Http::CodecType default_codec_type) {
55+ if (alpn == Http::Utility::AlpnNames::get ().Http11 ) {
56+ return Http::CodecType::HTTP1 ;
57+ }
58+ if (alpn == Http::Utility::AlpnNames::get ().Http2 ) {
59+ return Http::CodecType::HTTP2 ;
60+ }
61+ return default_codec_type;
62+ }
63+
4664} // namespace
4765
4866Upstream::HealthCheckerSharedPtr HttpHealthCheckerFactory::createCustomHealthChecker (
@@ -200,7 +218,7 @@ Http::Protocol codecClientTypeToProtocol(Http::CodecType codec_client_type) {
200218 PANIC_DUE_TO_CORRUPT_ENUM
201219}
202220
203- Http::Protocol HttpHealthCheckerImpl::protocol () const {
221+ Http::Protocol HttpHealthCheckerImpl::configuredProtocol () const {
204222 return codecClientTypeToProtocol (codec_client_type_);
205223}
206224
@@ -217,16 +235,30 @@ HttpHealthCheckerImpl::HttpActiveHealthCheckSession::HttpActiveHealthCheckSessio
217235
218236HttpHealthCheckerImpl::HttpActiveHealthCheckSession::~HttpActiveHealthCheckSession () {
219237 ASSERT (client_ == nullptr );
238+ ASSERT (pending_connection_ == nullptr );
220239}
221240
222241void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onDeferredDelete () {
242+ // If there is an active request or a connection being established it will get reset, so make
243+ // sure we ignore the reset. This must be set before closing, since close() raises LocalClose
244+ // synchronously.
245+ expect_reset_ = true ;
246+ resetPendingConnection ();
223247 if (client_) {
224- // If there is an active request it will get reset, so make sure we ignore the reset.
225- expect_reset_ = true ;
226248 client_->close (Network::ConnectionCloseType::Abort);
227249 }
228250}
229251
252+ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::resetPendingConnection () {
253+ if (pending_connection_ == nullptr ) {
254+ return ;
255+ }
256+ pending_connection_->removeConnectionCallbacks (pending_connection_callback_impl_);
257+ pending_connection_->close (Network::ConnectionCloseType::Abort);
258+ pending_host_description_.reset ();
259+ parent_.dispatcher_ .deferredDelete (std::move (pending_connection_));
260+ }
261+
230262void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::decodeHeaders (
231263 Http::ResponseHeaderMapPtr&& headers, bool end_stream) {
232264 ASSERT (!response_headers_);
@@ -269,6 +301,7 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onEvent(Network::Conne
269301// TODO(lilika) : Support connection pooling
270302void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval () {
271303 if (!client_) {
304+ ASSERT (pending_connection_ == nullptr );
272305 Upstream::Host::CreateConnectionData conn =
273306 host_->createHealthCheckConnection (parent_.dispatcher_ , parent_.transportSocketOptions (),
274307 parent_.transportSocketMatchMetadata ().get ());
@@ -279,13 +312,90 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() {
279312 handleFailure (envoy::data::core::v3::NETWORK );
280313 return ;
281314 }
282- client_.reset (parent_.createCodecClient (conn));
283- client_->addConnectionCallbacks (connection_callback_impl_);
284- client_->setCodecConnectionCallbacks (http_connection_callback_impl_);
315+
316+ // ALPN is only negotiated on secure transports, and a QUIC connection reports no negotiated
317+ // protocol, so HTTP/3 has nothing to select a codec from. In every other case the configured
318+ // codec is the only possible answer, so the codec client is created up front and the request
319+ // is sent while the connection is still being established, as it has always been.
320+ if (useNegotiatedProtocol () && parent_.codec_client_type_ != Http::CodecType::HTTP3 &&
321+ conn.connection_ ->ssl () != nullptr ) {
322+ // Reset these before connecting: a leftover `expect_reset_` from a previous timeout would
323+ // otherwise suppress the failure for this attempt.
324+ expect_reset_ = false ;
325+ reuse_connection_ = parent_.reuse_connection_ ;
326+ pending_host_description_ = conn.host_description_ ;
327+ pending_connection_ = std::move (conn.connection_ );
328+ pending_connection_->addConnectionCallbacks (pending_connection_callback_impl_);
329+ // Apply the connection settings that the codec client would otherwise have applied before
330+ // connecting, so that the connect and the handshake behave as they did when the codec client
331+ // was created up front.
332+ pending_connection_->detectEarlyCloseWhenReadDisabled (false );
333+ pending_connection_->noDelay (true );
334+ // The codec is chosen from the negotiated protocol and the request is sent once the
335+ // connection is established. See onPendingConnectionEvent().
336+ pending_connection_->connect ();
337+ return ;
338+ }
339+
340+ attachCodecClient (conn, parent_.codec_client_type_ );
285341 expect_reset_ = false ;
286342 reuse_connection_ = parent_.reuse_connection_ ;
287343 }
288344
345+ sendRequest ();
346+ }
347+
348+ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::attachCodecClient (
349+ Upstream::Host::CreateConnectionData& data, Http::CodecType codec_type) {
350+ client_.reset (parent_.createCodecClient (data, codec_type));
351+ client_->addConnectionCallbacks (connection_callback_impl_);
352+ client_->setCodecConnectionCallbacks (http_connection_callback_impl_);
353+ protocol_ = codecClientTypeToProtocol (codec_type);
354+ }
355+
356+ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onPendingConnectionEvent (
357+ Network::ConnectionEvent event) {
358+ ASSERT (pending_connection_ != nullptr );
359+
360+ if (event == Network::ConnectionEvent::RemoteClose ||
361+ event == Network::ConnectionEvent::LocalClose) {
362+ ENVOY_CONN_LOG (debug, " connect failure reason={} health_flags={}" , *pending_connection_,
363+ pending_connection_->transportFailureReason (),
364+ HostUtility::healthFlagsToString (*host_));
365+ pending_connection_->removeConnectionCallbacks (pending_connection_callback_impl_);
366+ pending_host_description_.reset ();
367+ parent_.dispatcher_ .deferredDelete (std::move (pending_connection_));
368+ if (!expect_reset_) {
369+ // handleFailure() may deferred delete this session, so nothing may be touched afterwards.
370+ handleFailure (envoy::data::core::v3::NETWORK );
371+ }
372+ return ;
373+ }
374+
375+ // Both Connected and ConnectedZeroRtt mean the handshake is done.
376+ if (event != Network::ConnectionEvent::Connected &&
377+ event != Network::ConnectionEvent::ConnectedZeroRtt) {
378+ return ;
379+ }
380+
381+ // The negotiated protocol - if any - is now known. Anything other than a protocol this health
382+ // checker can speak falls back to the configured codec.
383+ const std::string alpn = pending_connection_->nextProtocol ();
384+ const Http::CodecType codec_type = codecTypeFromAlpn (alpn, parent_.codec_client_type_ );
385+ ENVOY_CONN_LOG (debug, " health check negotiated alpn='{}', using {}" , *pending_connection_, alpn,
386+ Http::Utility::getProtocolString (codecClientTypeToProtocol (codec_type)));
387+
388+ // Hand the established connection over to a codec client. Adding and removing connection
389+ // callbacks while this event is being delivered is safe, and the codec client will simply see
390+ // the same Connected event once this returns.
391+ pending_connection_->removeConnectionCallbacks (pending_connection_callback_impl_);
392+ Upstream::Host::CreateConnectionData data{std::move (pending_connection_),
393+ std::move (pending_host_description_)};
394+ attachCodecClient (data, codec_type);
395+ sendRequest ();
396+ }
397+
398+ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::sendRequest () {
289399 Http::RequestEncoder* request_encoder = &client_->newStream (*this );
290400 request_encoder->getStream ().addCallbacks (*this );
291401 request_in_flight_ = true ;
@@ -474,6 +584,15 @@ bool HttpHealthCheckerImpl::HttpActiveHealthCheckSession::shouldClose() const {
474584
475585void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onTimeout () {
476586 request_in_flight_ = false ;
587+ if (pending_connection_) {
588+ ENVOY_CONN_LOG (debug, " connect timeout health_flags={}" , *pending_connection_,
589+ HostUtility::healthFlagsToString (*host_));
590+ // The caller records the timeout as a failure. resetPendingConnection() detaches the callbacks
591+ // before closing, so the close it triggers is not reported a second time.
592+ resetPendingConnection ();
593+ return ;
594+ }
595+
477596 if (client_) {
478597 ENVOY_CONN_LOG (debug, " connection/stream timeout health_flags={}" , *client_,
479598 HostUtility::healthFlagsToString (*host_));
@@ -500,10 +619,10 @@ HttpHealthCheckerImpl::codecClientType(const envoy::type::v3::CodecClientType& t
500619}
501620
502621Http::CodecClient*
503- ProdHttpHealthCheckerImpl::createCodecClient (Upstream::Host::CreateConnectionData& data) {
504- return new Http::CodecClientProd (codec_client_type_, std::move (data. connection_ ),
505- data.host_description_ , dispatcher_, random_generator_ ,
506- transportSocketOptions ());
622+ ProdHttpHealthCheckerImpl::createCodecClient (Upstream::Host::CreateConnectionData& data,
623+ Http::CodecType codec_type) {
624+ return new Http::CodecClientProd (codec_type, std::move ( data.connection_ ), data. host_description_ ,
625+ dispatcher_, random_generator_, transportSocketOptions ());
507626}
508627
509628} // namespace Upstream
0 commit comments