diff --git a/contrib/vcl/source/vcl_io_handle.cc b/contrib/vcl/source/vcl_io_handle.cc index 1dc67293ba15..3caa617be5ee 100644 --- a/contrib/vcl/source/vcl_io_handle.cc +++ b/contrib/vcl/source/vcl_io_handle.cc @@ -765,6 +765,8 @@ IoHandlePtr VclIoHandle::duplicate() { absl::optional VclIoHandle::lastRoundTripTime() { return {}; } +absl::optional VclIoHandle::congestionWindowInBytes() const { return {}; } + } // namespace Vcl } // namespace Network } // namespace Extensions diff --git a/contrib/vcl/source/vcl_io_handle.h b/contrib/vcl/source/vcl_io_handle.h index f878c3647fdf..fc2dce4abfb5 100644 --- a/contrib/vcl/source/vcl_io_handle.h +++ b/contrib/vcl/source/vcl_io_handle.h @@ -45,6 +45,7 @@ class VclIoHandle : public Envoy::Network::IoHandle, Api::IoCallUint64Result recvmmsg(RawSliceArrays& slices, uint32_t self_port, RecvMsgOutput& output) override; absl::optional lastRoundTripTime() override; + absl::optional congestionWindowInBytes() const override; bool supportsMmsg() const override; bool supportsUdpGro() const override { return false; } diff --git a/envoy/api/os_sys_calls.h b/envoy/api/os_sys_calls.h index 3c9b6f9698ac..0421a389d811 100644 --- a/envoy/api/os_sys_calls.h +++ b/envoy/api/os_sys_calls.h @@ -19,6 +19,9 @@ namespace Api { struct EnvoyTcpInfo { std::chrono::microseconds tcpi_rtt; + // Congestion window, in bytes. Note that posix's TCP_INFO socket option returns cwnd in packets, + // we multiply it by MSS to get bytes. + uint32_t tcpi_snd_cwnd = 0; }; // Small struct to avoid exposing ifaddrs -- which is not defined in all platforms -- to the diff --git a/envoy/network/connection.h b/envoy/network/connection.h index 79b25dac406c..433d16c3df4c 100644 --- a/envoy/network/connection.h +++ b/envoy/network/connection.h @@ -341,6 +341,14 @@ class Connection : public Event::DeferredDeletable, */ virtual void configureInitialCongestionWindow(uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt) PURE; + + /** + * @return the current congestion window in bytes, or unset if not available or not + * congestion-controlled. + * @note some congestion controller's cwnd is measured in number of packets, in that case the + * return value is cwnd(in packets) times the connection's MSS. + */ + virtual absl::optional congestionWindowInBytes() const PURE; }; using ConnectionPtr = std::unique_ptr; diff --git a/envoy/network/io_handle.h b/envoy/network/io_handle.h index 87a53a7709c3..db7dd90d30f5 100644 --- a/envoy/network/io_handle.h +++ b/envoy/network/io_handle.h @@ -327,6 +327,14 @@ class IoHandle { */ virtual absl::optional lastRoundTripTime() PURE; + /** + * @return the current congestion window in bytes, or unset if not available or not + * congestion-controlled. + * @note some congestion controller's cwnd is measured in number of packets, in that case the + * return value is cwnd(in packets) times the connection's MSS. + */ + virtual absl::optional congestionWindowInBytes() const PURE; + /** * @return the interface name for the socket, if the OS supports it. Otherwise, absl::nullopt. */ diff --git a/envoy/network/listen_socket.h b/envoy/network/listen_socket.h index a900ad7ee5e1..e600d2d0e42c 100644 --- a/envoy/network/listen_socket.h +++ b/envoy/network/listen_socket.h @@ -75,6 +75,14 @@ class ConnectionSocket : public virtual Socket, public virtual ScopeTrackedObjec * returned. */ virtual absl::optional lastRoundTripTime() PURE; + + /** + * @return the current congestion window in bytes, or unset if not available or not + * congestion-controlled. + * @note some congestion controller's cwnd is measured in number of packets, in that case the + * return value is cwnd(in packets) times the connection's MSS. + */ + virtual absl::optional congestionWindowInBytes() const PURE; }; using ConnectionSocketPtr = std::unique_ptr; diff --git a/source/common/api/posix/os_sys_calls_impl.cc b/source/common/api/posix/os_sys_calls_impl.cc index 65fa8ecad506..a7c241bc2047 100644 --- a/source/common/api/posix/os_sys_calls_impl.cc +++ b/source/common/api/posix/os_sys_calls_impl.cc @@ -290,6 +290,10 @@ SysCallBoolResult OsSysCallsImpl::socketTcpInfo([[maybe_unused]] os_fd_t sockfd, auto result = ::getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &unix_tcp_info, &len); if (!SOCKET_FAILURE(result)) { tcp_info->tcpi_rtt = std::chrono::microseconds(unix_tcp_info.tcpi_rtt); + + const uint64_t mss = (unix_tcp_info.tcpi_snd_mss > 0) ? unix_tcp_info.tcpi_snd_mss : 1460; + // Convert packets to bytes. + tcp_info->tcpi_snd_cwnd = unix_tcp_info.tcpi_snd_cwnd * mss; } return {!SOCKET_FAILURE(result), !SOCKET_FAILURE(result) ? 0 : errno}; #endif diff --git a/source/common/api/win32/os_sys_calls_impl.cc b/source/common/api/win32/os_sys_calls_impl.cc index b08f07f8d858..fc8ac700d3e0 100644 --- a/source/common/api/win32/os_sys_calls_impl.cc +++ b/source/common/api/win32/os_sys_calls_impl.cc @@ -403,6 +403,7 @@ SysCallBoolResult OsSysCallsImpl::socketTcpInfo([[maybe_unused]] os_fd_t sockfd, if (!SOCKET_FAILURE(rc)) { tcp_info->tcpi_rtt = std::chrono::microseconds(win_tcpinfo.RttUs); + tcp_info->tcpi_snd_cwnd = win_tcpinfo.Cwnd; } return {!SOCKET_FAILURE(rc), !SOCKET_FAILURE(rc) ? 0 : ::WSAGetLastError()}; #endif diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 926457190801..a8c54da16736 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -788,6 +788,10 @@ void ConnectionImpl::configureInitialCongestionWindow(uint64_t bandwidth_bits_pe return transport_socket_->configureInitialCongestionWindow(bandwidth_bits_per_sec, rtt); } +absl::optional ConnectionImpl::congestionWindowInBytes() const { + return socket_->congestionWindowInBytes(); +} + void ConnectionImpl::flushWriteBuffer() { if (state() == State::Open && write_buffer_->length() > 0) { onWriteReady(); diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index a775d17c9d2c..0882ac9af802 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -101,6 +101,7 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback absl::optional lastRoundTripTime() const override; void configureInitialCongestionWindow(uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt) override; + absl::optional congestionWindowInBytes() const override; // Network::FilterManagerConnection void rawWrite(Buffer::Instance& data, bool end_stream) override; diff --git a/source/common/network/happy_eyeballs_connection_impl.cc b/source/common/network/happy_eyeballs_connection_impl.cc index 39431d834175..cbe31161771c 100644 --- a/source/common/network/happy_eyeballs_connection_impl.cc +++ b/source/common/network/happy_eyeballs_connection_impl.cc @@ -282,6 +282,11 @@ absl::optional HappyEyeballsConnectionImpl::lastRound return connections_[0]->lastRoundTripTime(); } +absl::optional HappyEyeballsConnectionImpl::congestionWindowInBytes() const { + // Note, this value changes constantly even within the same connection. + return connections_[0]->congestionWindowInBytes(); +} + void HappyEyeballsConnectionImpl::addConnectionCallbacks(ConnectionCallbacks& cb) { if (connect_finished_) { connections_[0]->addConnectionCallbacks(cb); diff --git a/source/common/network/happy_eyeballs_connection_impl.h b/source/common/network/happy_eyeballs_connection_impl.h index 57acb7fc13d6..d330bdcc5e8f 100644 --- a/source/common/network/happy_eyeballs_connection_impl.h +++ b/source/common/network/happy_eyeballs_connection_impl.h @@ -69,6 +69,7 @@ class HappyEyeballsConnectionImpl : public ClientConnection, bool startSecureTransport() override; absl::optional lastRoundTripTime() const override; void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {} + absl::optional congestionWindowInBytes() const override; // Simple getters which always delegate to the first connection in connections_. bool isHalfCloseEnabled() override; diff --git a/source/common/network/io_socket_handle_impl.cc b/source/common/network/io_socket_handle_impl.cc index c40e9f03d77e..9f7a2962fda0 100644 --- a/source/common/network/io_socket_handle_impl.cc +++ b/source/common/network/io_socket_handle_impl.cc @@ -576,6 +576,15 @@ absl::optional IoSocketHandleImpl::lastRoundTripTime( return std::chrono::duration_cast(info.tcpi_rtt); } +absl::optional IoSocketHandleImpl::congestionWindowInBytes() const { + Api::EnvoyTcpInfo info; + auto result = Api::OsSysCallsSingleton::get().socketTcpInfo(fd_, &info); + if (!result.return_value_) { + return {}; + } + return info.tcpi_snd_cwnd; +} + absl::optional IoSocketHandleImpl::interfaceName() { auto& os_syscalls_singleton = Api::OsSysCallsSingleton::get(); if (!os_syscalls_singleton.supportsGetifaddrs()) { diff --git a/source/common/network/io_socket_handle_impl.h b/source/common/network/io_socket_handle_impl.h index fe57fff4073a..7bc007a2e7ed 100644 --- a/source/common/network/io_socket_handle_impl.h +++ b/source/common/network/io_socket_handle_impl.h @@ -80,6 +80,7 @@ class IoSocketHandleImpl : public IoHandle, protected Logger::Loggable lastRoundTripTime() override; + absl::optional congestionWindowInBytes() const override; absl::optional interfaceName() override; protected: diff --git a/source/common/network/listen_socket_impl.h b/source/common/network/listen_socket_impl.h index 14e516671883..e190be417719 100644 --- a/source/common/network/listen_socket_impl.h +++ b/source/common/network/listen_socket_impl.h @@ -228,6 +228,10 @@ class ConnectionSocketImpl : public SocketImpl, public ConnectionSocket { return ioHandle().lastRoundTripTime(); } + absl::optional congestionWindowInBytes() const override { + return ioHandle().congestionWindowInBytes(); + } + void dumpState(std::ostream& os, int indent_level) const override { const char* spaces = spacesForLevel(indent_level); os << spaces << "ListenSocketImpl " << this << DUMP_MEMBER(transport_protocol_) << "\n"; diff --git a/source/common/quic/quic_filter_manager_connection_impl.cc b/source/common/quic/quic_filter_manager_connection_impl.cc index 1cba93673f73..26d48eb50c97 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.cc +++ b/source/common/quic/quic_filter_manager_connection_impl.cc @@ -217,6 +217,20 @@ void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() { } } +absl::optional +QuicFilterManagerConnectionImpl::lastRoundTripTime() const { + if (quicConnection() == nullptr) { + return {}; + } + + const auto* rtt_stats = quicConnection()->sent_packet_manager().GetRttStats(); + if (!rtt_stats->latest_rtt().IsZero()) { + return std::chrono::milliseconds(rtt_stats->latest_rtt().ToMilliseconds()); + } + + return std::chrono::milliseconds(rtt_stats->initial_rtt().ToMilliseconds()); +} + void QuicFilterManagerConnectionImpl::configureInitialCongestionWindow( uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt) { if (quicConnection() != nullptr) { @@ -231,5 +245,18 @@ void QuicFilterManagerConnectionImpl::configureInitialCongestionWindow( } } +absl::optional QuicFilterManagerConnectionImpl::congestionWindowInBytes() const { + if (quicConnection() == nullptr) { + return {}; + } + + uint64_t cwnd = quicConnection()->sent_packet_manager().GetCongestionWindowInBytes(); + if (cwnd == 0) { + return {}; + } + + return cwnd; +} + } // namespace Quic } // namespace Envoy diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index 4fe18f0abf75..04094e832dab 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -114,10 +114,10 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } absl::string_view transportFailureReason() const override { return transport_failure_reason_; } bool startSecureTransport() override { return false; } - // TODO(#2557) Implement this. - absl::optional lastRoundTripTime() const override { return {}; } + absl::optional lastRoundTripTime() const override; void configureInitialCongestionWindow(uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt) override; + absl::optional congestionWindowInBytes() const override; // Network::FilterManagerConnection void rawWrite(Buffer::Instance& data, bool end_stream) override; diff --git a/source/common/quic/quic_io_handle_wrapper.h b/source/common/quic/quic_io_handle_wrapper.h index db9087daff10..9e41647b625a 100644 --- a/source/common/quic/quic_io_handle_wrapper.h +++ b/source/common/quic/quic_io_handle_wrapper.h @@ -5,6 +5,7 @@ #include "envoy/network/io_handle.h" +#include "source/common/common/assert.h" #include "source/common/network/io_socket_error_impl.h" namespace Envoy { @@ -142,6 +143,12 @@ class QuicIoHandleWrapper : public Network::IoHandle { Api::SysCallIntResult shutdown(int how) override { return io_handle_.shutdown(how); } absl::optional lastRoundTripTime() override { return {}; } + absl::optional congestionWindowInBytes() const override { + // QUIC should get congestion window from QuicFilterManagerConnectionImpl, which implements the + // Envoy::Network::Connection::congestionWindowInBytes interface. + IS_ENVOY_BUG("QuicIoHandleWrapper does not implement congestionWindowInBytes."); + return {}; + } private: Network::IoHandle& io_handle_; diff --git a/source/extensions/io_socket/user_space/io_handle_impl.h b/source/extensions/io_socket/user_space/io_handle_impl.h index 9f7ab9010b8b..5b0bdf1daa31 100644 --- a/source/extensions/io_socket/user_space/io_handle_impl.h +++ b/source/extensions/io_socket/user_space/io_handle_impl.h @@ -88,6 +88,7 @@ class IoHandleImpl final : public Network::IoHandle, Api::SysCallIntResult shutdown(int how) override; absl::optional lastRoundTripTime() override { return absl::nullopt; } + absl::optional congestionWindowInBytes() const override { return absl::nullopt; } absl::optional interfaceName() override { return absl::nullopt; } void setWatermarks(uint32_t watermark) { pending_received_data_.setWatermarks(watermark); } diff --git a/source/server/api_listener_impl.h b/source/server/api_listener_impl.h index d96c8b442268..34bb413bdb4d 100644 --- a/source/server/api_listener_impl.h +++ b/source/server/api_listener_impl.h @@ -160,8 +160,9 @@ class ApiListenerImplBase : public ApiListener, IS_ENVOY_BUG("Unexpected function call"); return false; } - absl::optional lastRoundTripTime() const override { return {}; }; + absl::optional lastRoundTripTime() const override { return {}; } void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {} + absl::optional congestionWindowInBytes() const override { return {}; } // ScopeTrackedObject void dumpState(std::ostream& os, int) const override { os << "SyntheticConnection"; } diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 104e2a532fb7..344f14f4ab35 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -303,6 +303,23 @@ TEST_P(ConnectionImplTest, UniqueId) { disconnect(false); } +TEST_P(ConnectionImplTest, GetCongestionWindow) { + setUpBasicConnection(); + connect(); + +// Congestion window is available on Posix(guarded by TCP_INFO) and Windows(guarded by +// SIO_TCP_INFO). +#if defined(TCP_INFO) || defined(SIO_TCP_INFO) + EXPECT_GT(client_connection_->congestionWindowInBytes().value(), 500); + EXPECT_GT(server_connection_->congestionWindowInBytes().value(), 500); +#else + EXPECT_FALSE(client_connection_->congestionWindowInBytes().has_value()); + EXPECT_FALSE(server_connection_->congestionWindowInBytes().has_value()); +#endif + + disconnect(true); +} + TEST_P(ConnectionImplTest, CloseDuringConnectCallback) { setUpBasicConnection(); diff --git a/test/common/quic/client_connection_factory_impl_test.cc b/test/common/quic/client_connection_factory_impl_test.cc index 39f880c23e87..10a823b2e708 100644 --- a/test/common/quic/client_connection_factory_impl_test.cc +++ b/test/common/quic/client_connection_factory_impl_test.cc @@ -64,7 +64,7 @@ TEST_P(QuicNetworkConnectionTest, BufferLimits) { ASSERT(session != nullptr); EXPECT_EQ(highWatermark(session), 45); EXPECT_EQ(absl::nullopt, session->unixSocketPeerCredentials()); - EXPECT_EQ(absl::nullopt, session->lastRoundTripTime()); + EXPECT_NE(absl::nullopt, session->lastRoundTripTime()); client_connection->close(Network::ConnectionCloseType::NoFlush); } diff --git a/test/common/quic/envoy_quic_client_session_test.cc b/test/common/quic/envoy_quic_client_session_test.cc index 62a5579b6abd..2c7e38c51bc5 100644 --- a/test/common/quic/envoy_quic_client_session_test.cc +++ b/test/common/quic/envoy_quic_client_session_test.cc @@ -348,5 +348,16 @@ TEST_P(EnvoyQuicClientSessionTest, IncomingUnidirectionalReadStream) { envoy_quic_session_.OnStreamFrame(stream_frame2); } +TEST_P(EnvoyQuicClientSessionTest, GetRttAndCwnd) { + EXPECT_GT(envoy_quic_session_.lastRoundTripTime().value(), std::chrono::microseconds(0)); + // Just make sure the CWND is non-zero. We don't want to make strong assertions on what the value + // should be in this test, that is the job the congestion controllers' tests. + EXPECT_GT(envoy_quic_session_.congestionWindowInBytes().value(), 500); + + envoy_quic_session_.configureInitialCongestionWindow(8000000, std::chrono::microseconds(1000000)); + EXPECT_GT(envoy_quic_session_.congestionWindowInBytes().value(), + quic::kInitialCongestionWindow * quic::kDefaultTCPMSS); +} + } // namespace Quic } // namespace Envoy diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index db9cfea306ad..0a551e3928c3 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -1033,5 +1033,17 @@ TEST_F(EnvoyQuicServerSessionTest, IncomingUnidirectionalReadStream) { envoy_quic_session_.OnStreamFrame(stream_frame); } +TEST_F(EnvoyQuicServerSessionTest, GetRttAndCwnd) { + installReadFilter(); + EXPECT_GT(envoy_quic_session_.lastRoundTripTime().value(), std::chrono::microseconds(0)); + // Just make sure the CWND is non-zero. We don't want to make strong assertions on what the value + // should be in this test, that is the job the congestion controllers' tests. + EXPECT_GT(envoy_quic_session_.congestionWindowInBytes().value(), 500); + + envoy_quic_session_.configureInitialCongestionWindow(8000000, std::chrono::microseconds(1000000)); + EXPECT_GT(envoy_quic_session_.congestionWindowInBytes().value(), + quic::kInitialCongestionWindow * quic::kDefaultTCPMSS); +} + } // namespace Quic } // namespace Envoy diff --git a/test/mocks/network/connection.h b/test/mocks/network/connection.h index a98e298dcb58..e939c4395ba1 100644 --- a/test/mocks/network/connection.h +++ b/test/mocks/network/connection.h @@ -88,6 +88,7 @@ class MockConnectionBase { MOCK_METHOD(absl::optional, lastRoundTripTime, (), (const)); \ MOCK_METHOD(void, configureInitialCongestionWindow, \ (uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt), ()); \ + MOCK_METHOD(absl::optional, congestionWindowInBytes, (), (const)); \ MOCK_METHOD(void, dumpState, (std::ostream&, int), (const)); class MockConnection : public Connection, public MockConnectionBase { diff --git a/test/mocks/network/io_handle.h b/test/mocks/network/io_handle.h index 1ccb724d53fe..aff62657ae89 100644 --- a/test/mocks/network/io_handle.h +++ b/test/mocks/network/io_handle.h @@ -61,6 +61,7 @@ class MockIoHandle : public IoHandle { MOCK_METHOD(void, resetFileEvents, ()); MOCK_METHOD(Api::SysCallIntResult, shutdown, (int how)); MOCK_METHOD(absl::optional, lastRoundTripTime, ()); + MOCK_METHOD(absl::optional, congestionWindowInBytes, (), (const)); MOCK_METHOD(Api::SysCallIntResult, ioctl, (unsigned long, void*, unsigned long, void*, unsigned long, unsigned long*)); MOCK_METHOD(absl::optional, interfaceName, ()); diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index c5c1ab979ada..2acb8562d3e1 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -355,6 +355,7 @@ class MockConnectionSocket : public ConnectionSocket { (unsigned long, void*, unsigned long, void*, unsigned long, unsigned long*)); MOCK_METHOD(Api::SysCallIntResult, setBlockingForTest, (bool)); MOCK_METHOD(absl::optional, lastRoundTripTime, ()); + MOCK_METHOD(absl::optional, congestionWindowInBytes, (), (const)); MOCK_METHOD(void, dumpState, (std::ostream&, int), (const)); IoHandlePtr io_handle_; diff --git a/test/server/filter_chain_benchmark_test.cc b/test/server/filter_chain_benchmark_test.cc index e2d4a9b891e1..f793cb07bf23 100644 --- a/test/server/filter_chain_benchmark_test.cc +++ b/test/server/filter_chain_benchmark_test.cc @@ -128,6 +128,7 @@ class MockConnectionSocket : public Network::ConnectionSocket { } Api::SysCallIntResult setBlockingForTest(bool) override { return {0, 0}; } absl::optional lastRoundTripTime() override { return {}; } + absl::optional congestionWindowInBytes() const override { return {}; } void dumpState(std::ostream&, int) const override {} private: diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 21a1f18c3c6f..f2828c25eb13 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -320,6 +320,7 @@ SIGINT SIGPIPE SIGSEGV SIGTERM +SIO SMTP SNI SOTW