Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a congestionWindowInBytes method to Envoy::Network::Connection #20105

Merged
merged 8 commits into from
Mar 1, 2022
2 changes: 2 additions & 0 deletions contrib/vcl/source/vcl_io_handle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,8 @@ IoHandlePtr VclIoHandle::duplicate() {

absl::optional<std::chrono::milliseconds> VclIoHandle::lastRoundTripTime() { return {}; }

absl::optional<uint64_t> VclIoHandle::congestionWindowInBytes() const { return {}; }

} // namespace Vcl
} // namespace Network
} // namespace Extensions
Expand Down
1 change: 1 addition & 0 deletions contrib/vcl/source/vcl_io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class VclIoHandle : public Envoy::Network::IoHandle,
Api::IoCallUint64Result recvmmsg(RawSliceArrays& slices, uint32_t self_port,
RecvMsgOutput& output) override;
absl::optional<std::chrono::milliseconds> lastRoundTripTime() override;
absl::optional<uint64_t> congestionWindowInBytes() const override;

bool supportsMmsg() const override;
bool supportsUdpGro() const override { return false; }
Expand Down
3 changes: 3 additions & 0 deletions envoy/api/os_sys_calls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions envoy/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t> congestionWindowInBytes() const PURE;
};

using ConnectionPtr = std::unique_ptr<Connection>;
Expand Down
8 changes: 8 additions & 0 deletions envoy/network/io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ class IoHandle {
*/
virtual absl::optional<std::chrono::milliseconds> 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<uint64_t> congestionWindowInBytes() const PURE;

/**
* @return the interface name for the socket, if the OS supports it. Otherwise, absl::nullopt.
*/
Expand Down
8 changes: 8 additions & 0 deletions envoy/network/listen_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ class ConnectionSocket : public virtual Socket, public virtual ScopeTrackedObjec
* returned.
*/
virtual absl::optional<std::chrono::milliseconds> 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<uint64_t> congestionWindowInBytes() const PURE;
};

using ConnectionSocketPtr = std::unique_ptr<ConnectionSocket>;
Expand Down
4 changes: 4 additions & 0 deletions source/common/api/posix/os_sys_calls_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions source/common/api/win32/os_sys_calls_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,10 @@ void ConnectionImpl::configureInitialCongestionWindow(uint64_t bandwidth_bits_pe
return transport_socket_->configureInitialCongestionWindow(bandwidth_bits_per_sec, rtt);
}

absl::optional<uint64_t> ConnectionImpl::congestionWindowInBytes() const {
return socket_->congestionWindowInBytes();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually can directly access socket->ioHandle() here. Can we skip ConnectionSocket::congestionWindowInBytes() interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following ConnectionImpl::lastRoundTripTime() here. I don't mind directly access socket->ioHandle() from here, but it's better for lastRoundTripTime and congestionWindowInBytes to do things consistently.

@mattklein123 : WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real opinion. Whatever you both think is fine with me though I agree about being consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Matt. I talked with Dan offline and we'll leave it as is.

}

void ConnectionImpl::flushWriteBuffer() {
if (state() == State::Open && write_buffer_->length() > 0) {
onWriteReady();
Expand Down
1 change: 1 addition & 0 deletions source/common/network/connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback
absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override;
void configureInitialCongestionWindow(uint64_t bandwidth_bits_per_sec,
std::chrono::microseconds rtt) override;
absl::optional<uint64_t> congestionWindowInBytes() const override;

// Network::FilterManagerConnection
void rawWrite(Buffer::Instance& data, bool end_stream) override;
Expand Down
5 changes: 5 additions & 0 deletions source/common/network/happy_eyeballs_connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ absl::optional<std::chrono::milliseconds> HappyEyeballsConnectionImpl::lastRound
return connections_[0]->lastRoundTripTime();
}

absl::optional<uint64_t> 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);
Expand Down
1 change: 1 addition & 0 deletions source/common/network/happy_eyeballs_connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class HappyEyeballsConnectionImpl : public ClientConnection,
bool startSecureTransport() override;
absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override;
void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {}
absl::optional<uint64_t> congestionWindowInBytes() const override;

// Simple getters which always delegate to the first connection in connections_.
bool isHalfCloseEnabled() override;
Expand Down
9 changes: 9 additions & 0 deletions source/common/network/io_socket_handle_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ absl::optional<std::chrono::milliseconds> IoSocketHandleImpl::lastRoundTripTime(
return std::chrono::duration_cast<std::chrono::milliseconds>(info.tcpi_rtt);
}

absl::optional<uint64_t> 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<std::string> IoSocketHandleImpl::interfaceName() {
auto& os_syscalls_singleton = Api::OsSysCallsSingleton::get();
if (!os_syscalls_singleton.supportsGetifaddrs()) {
Expand Down
1 change: 1 addition & 0 deletions source/common/network/io_socket_handle_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class IoSocketHandleImpl : public IoHandle, protected Logger::Loggable<Logger::I

Api::SysCallIntResult shutdown(int how) override;
absl::optional<std::chrono::milliseconds> lastRoundTripTime() override;
absl::optional<uint64_t> congestionWindowInBytes() const override;
absl::optional<std::string> interfaceName() override;

protected:
Expand Down
4 changes: 4 additions & 0 deletions source/common/network/listen_socket_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ class ConnectionSocketImpl : public SocketImpl, public ConnectionSocket {
return ioHandle().lastRoundTripTime();
}

absl::optional<uint64_t> 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";
Expand Down
27 changes: 27 additions & 0 deletions source/common/quic/quic_filter_manager_connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ void QuicFilterManagerConnectionImpl::onSendBufferLowWatermark() {
}
}

absl::optional<std::chrono::milliseconds>
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) {
Expand All @@ -231,5 +245,18 @@ void QuicFilterManagerConnectionImpl::configureInitialCongestionWindow(
}
}

absl::optional<uint64_t> 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
4 changes: 2 additions & 2 deletions source/common/quic/quic_filter_manager_connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::chrono::milliseconds> lastRoundTripTime() const override { return {}; }
absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override;
void configureInitialCongestionWindow(uint64_t bandwidth_bits_per_sec,
std::chrono::microseconds rtt) override;
absl::optional<uint64_t> congestionWindowInBytes() const override;

// Network::FilterManagerConnection
void rawWrite(Buffer::Instance& data, bool end_stream) override;
Expand Down
7 changes: 7 additions & 0 deletions source/common/quic/quic_io_handle_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -142,6 +143,12 @@ class QuicIoHandleWrapper : public Network::IoHandle {

Api::SysCallIntResult shutdown(int how) override { return io_handle_.shutdown(how); }
absl::optional<std::chrono::milliseconds> lastRoundTripTime() override { return {}; }
absl::optional<uint64_t> 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_;
Expand Down
1 change: 1 addition & 0 deletions source/extensions/io_socket/user_space/io_handle_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class IoHandleImpl final : public Network::IoHandle,

Api::SysCallIntResult shutdown(int how) override;
absl::optional<std::chrono::milliseconds> lastRoundTripTime() override { return absl::nullopt; }
absl::optional<uint64_t> congestionWindowInBytes() const override { return absl::nullopt; }
absl::optional<std::string> interfaceName() override { return absl::nullopt; }

void setWatermarks(uint32_t watermark) { pending_received_data_.setWatermarks(watermark); }
Expand Down
3 changes: 2 additions & 1 deletion source/server/api_listener_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ class ApiListenerImplBase : public ApiListener,
IS_ENVOY_BUG("Unexpected function call");
return false;
}
absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override { return {}; };
absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override { return {}; }
void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {}
absl::optional<uint64_t> congestionWindowInBytes() const override { return {}; }
// ScopeTrackedObject
void dumpState(std::ostream& os, int) const override { os << "SyntheticConnection"; }

Expand Down
17 changes: 17 additions & 0 deletions test/common/network/connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
wu-bin marked this conversation as resolved.
Show resolved Hide resolved
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();

Expand Down
2 changes: 1 addition & 1 deletion test/common/quic/client_connection_factory_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
11 changes: 11 additions & 0 deletions test/common/quic/envoy_quic_client_session_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions test/common/quic/envoy_quic_server_session_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions test/mocks/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class MockConnectionBase {
MOCK_METHOD(absl::optional<std::chrono::milliseconds>, lastRoundTripTime, (), (const)); \
MOCK_METHOD(void, configureInitialCongestionWindow, \
(uint64_t bandwidth_bits_per_sec, std::chrono::microseconds rtt), ()); \
MOCK_METHOD(absl::optional<uint64_t>, congestionWindowInBytes, (), (const)); \
MOCK_METHOD(void, dumpState, (std::ostream&, int), (const));

class MockConnection : public Connection, public MockConnectionBase {
Expand Down
1 change: 1 addition & 0 deletions test/mocks/network/io_handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class MockIoHandle : public IoHandle {
MOCK_METHOD(void, resetFileEvents, ());
MOCK_METHOD(Api::SysCallIntResult, shutdown, (int how));
MOCK_METHOD(absl::optional<std::chrono::milliseconds>, lastRoundTripTime, ());
MOCK_METHOD(absl::optional<uint64_t>, congestionWindowInBytes, (), (const));
MOCK_METHOD(Api::SysCallIntResult, ioctl,
(unsigned long, void*, unsigned long, void*, unsigned long, unsigned long*));
MOCK_METHOD(absl::optional<std::string>, interfaceName, ());
Expand Down
1 change: 1 addition & 0 deletions test/mocks/network/mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::chrono::milliseconds>, lastRoundTripTime, ());
MOCK_METHOD(absl::optional<uint64_t>, congestionWindowInBytes, (), (const));
MOCK_METHOD(void, dumpState, (std::ostream&, int), (const));

IoHandlePtr io_handle_;
Expand Down
1 change: 1 addition & 0 deletions test/server/filter_chain_benchmark_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class MockConnectionSocket : public Network::ConnectionSocket {
}
Api::SysCallIntResult setBlockingForTest(bool) override { return {0, 0}; }
absl::optional<std::chrono::milliseconds> lastRoundTripTime() override { return {}; }
absl::optional<uint64_t> congestionWindowInBytes() const override { return {}; }
void dumpState(std::ostream&, int) const override {}

private:
Expand Down
1 change: 1 addition & 0 deletions tools/spelling/spelling_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ SIGINT
SIGPIPE
SIGSEGV
SIGTERM
SIO
SMTP
SNI
SOTW
Expand Down