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

perf: add config to enable looking up local interface name for upstream connections #19758

Merged
merged 22 commits into from
Mar 16, 2022
Merged
4 changes: 4 additions & 0 deletions api/envoy/config/cluster/v3/cluster.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,10 @@ message UpstreamConnectionOptions {

// If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives.
core.v3.TcpKeepalive tcp_keepalive = 1;

// If true, Envoy will make the interface name of the local address for the upstream connection
junr03 marked this conversation as resolved.
Show resolved Hide resolved
// available for debugging purposes. Defaults to false due to performance concerns.
junr03 marked this conversation as resolved.
Show resolved Hide resolved
bool set_local_interface_name_on_upstream_connections = 2;
junr03 marked this conversation as resolved.
Show resolved Hide resolved
}

message TrackClusterStats {
Expand Down
1 change: 1 addition & 0 deletions envoy/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class Connection : public Event::DeferredDeletable,
/**
* @return the connection info provider backing this connection.
*/
virtual ConnectionInfoSetter& connectionInfoProvider() PURE;
junr03 marked this conversation as resolved.
Show resolved Hide resolved
virtual const ConnectionInfoProvider& connectionInfoProvider() const PURE;
virtual ConnectionInfoProviderSharedPtr connectionInfoProviderSharedPtr() const PURE;

Expand Down
7 changes: 6 additions & 1 deletion envoy/network/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,16 @@ class ConnectionInfoSetter : public ConnectionInfoProvider {
**/
virtual void setConnectionID(uint64_t id) PURE;

/**
* @param enable whether to enable or disable setting interface name.
*/
virtual void enableSettingInterfaceName(const bool enable) PURE;

/**
* @param interface_name the name of the network interface used by the local end of the
*connection.
junr03 marked this conversation as resolved.
Show resolved Hide resolved
**/
virtual void setInterfaceName(absl::string_view interface_name) PURE;
virtual void maybeSetInterfaceName(IoHandle& io_handle) PURE;

/**
* @param connection_info sets the downstream ssl connection.
Expand Down
5 changes: 5 additions & 0 deletions envoy/upstream/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,11 @@ class ClusterInfo {
*/
virtual bool warmHosts() const PURE;

/**
* @return true if this cluster is configured to set local interface name on upstream connections.
*/
virtual bool setLocalInterfaceNameOnUpstreamConnections() const PURE;

/**
* @return eds cluster service_name of the cluster.
*/
Expand Down
5 changes: 2 additions & 3 deletions source/common/network/connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -929,12 +929,11 @@ void ClientConnectionImpl::onConnected() {
stream_info_.upstreamInfo()->upstreamTiming().onUpstreamConnectComplete(dispatcher_.timeSource());
// There are no meaningful socket source address semantics for non-IP sockets, so skip.
if (socket_->connectionInfoProviderSharedPtr()->remoteAddress()->ip()) {
// interfaceName makes a syscall. Call once to minimize perf hit.
const auto maybe_interface_name = ioHandle().interfaceName();
socket_->connectionInfoProvider().maybeSetInterfaceName(ioHandle());
junr03 marked this conversation as resolved.
Show resolved Hide resolved
const auto maybe_interface_name = socket_->connectionInfoProvider().interfaceName();
if (maybe_interface_name.has_value()) {
ENVOY_CONN_LOG_EVENT(debug, "conn_interface", "connected on local interface '{}'", *this,
maybe_interface_name.value());
socket_->connectionInfoProvider().setInterfaceName(maybe_interface_name.value());
}
}
ConnectionImpl::onConnected();
Expand Down
3 changes: 3 additions & 0 deletions source/common/network/connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ class ConnectionImpl : public ConnectionImplBase, public TransportSocketCallback
void readDisable(bool disable) override;
void detectEarlyCloseWhenReadDisabled(bool value) override { detect_early_close_ = value; }
bool readEnabled() const override;
ConnectionInfoSetter& connectionInfoProvider() override {
return socket_->connectionInfoProvider();
}
const ConnectionInfoProvider& connectionInfoProvider() const override {
return socket_->connectionInfoProvider();
}
Expand Down
4 changes: 4 additions & 0 deletions source/common/network/happy_eyeballs_connection_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ bool HappyEyeballsConnectionImpl::readEnabled() const {
return connections_[0]->readEnabled();
}

ConnectionInfoSetter& HappyEyeballsConnectionImpl::connectionInfoProvider() {
return connections_[0]->connectionInfoProvider();
}

const ConnectionInfoProvider& HappyEyeballsConnectionImpl::connectionInfoProvider() const {
return connections_[0]->connectionInfoProvider();
}
Expand Down
2 changes: 2 additions & 0 deletions source/common/network/happy_eyeballs_connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class HappyEyeballsConnectionImpl : public ClientConnection,
bool isHalfCloseEnabled() override;
std::string nextProtocol() const override;
// Note, this might change before connect finishes.
ConnectionInfoSetter& connectionInfoProvider() override;
// Note, this might change before connect finishes.
junr03 marked this conversation as resolved.
Show resolved Hide resolved
const ConnectionInfoProvider& connectionInfoProvider() const override;
// Note, this might change before connect finishes.
ConnectionInfoProviderSharedPtr connectionInfoProviderSharedPtr() const override;
Expand Down
10 changes: 8 additions & 2 deletions source/common/network/socket_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ class ConnectionInfoSetterImpl : public ConnectionInfoSetter {
absl::optional<uint64_t> connectionID() const override { return connection_id_; }
void setConnectionID(uint64_t id) override { connection_id_ = id; }
absl::optional<absl::string_view> interfaceName() const override { return interface_name_; }
void setInterfaceName(absl::string_view interface_name) override {
interface_name_ = std::string(interface_name);
void enableSettingInterfaceName(const bool enable) override {
allow_syscall_for_interface_name_ = enable;
}
void maybeSetInterfaceName(IoHandle& io_handle) override {
if (allow_syscall_for_interface_name_) {
interface_name_ = io_handle.interfaceName();
}
}
Ssl::ConnectionInfoConstSharedPtr sslConnection() const override { return ssl_info_; }
void setSslConnection(const Ssl::ConnectionInfoConstSharedPtr& ssl_connection_info) override {
Expand All @@ -71,6 +76,7 @@ class ConnectionInfoSetterImpl : public ConnectionInfoSetter {
Address::InstanceConstSharedPtr direct_remote_address_;
std::string server_name_;
absl::optional<uint64_t> connection_id_;
bool allow_syscall_for_interface_name_{false};
absl::optional<std::string> interface_name_;
Ssl::ConnectionInfoConstSharedPtr ssl_info_;
std::string ja3_hash_;
Expand Down
5 changes: 5 additions & 0 deletions source/common/quic/quic_filter_manager_connection_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase,
void readDisable(bool /*disable*/) override { ASSERT(false); }
void detectEarlyCloseWhenReadDisabled(bool /*value*/) override { ASSERT(false); }
bool readEnabled() const override { return true; }
Network::ConnectionInfoSetter& connectionInfoProvider() override {
ENVOY_BUG(network_connection_ && network_connection_->connectionSocket(),
"No connection socket.");
return network_connection_->connectionSocket()->connectionInfoProvider();
}
const Network::ConnectionInfoSetter& connectionInfoProvider() const override {
ENVOY_BUG(network_connection_ && network_connection_->connectionSocket(),
"No connection socket.");
Expand Down
4 changes: 4 additions & 0 deletions source/common/upstream/upstream_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ Network::ClientConnectionPtr HostImpl::createConnection(
socket_factory.createTransportSocket(std::move(transport_socket_options)),
connection_options);

connection->connectionInfoProvider().enableSettingInterfaceName(
cluster.setLocalInterfaceNameOnUpstreamConnections());
connection->setBufferLimits(cluster.perConnectionBufferLimitBytes());
cluster.createNetworkFilterChain(*connection);
return connection;
Expand Down Expand Up @@ -843,6 +845,8 @@ ClusterInfoImpl::ClusterInfoImpl(
config.connection_pool_per_downstream_connection()),
warm_hosts_(!config.health_checks().empty() &&
common_lb_config_.ignore_new_hosts_until_first_hc()),
set_local_interface_name_on_upstream_connections_(
config.upstream_connection_options().set_local_interface_name_on_upstream_connections()),
cluster_type_(
config.has_cluster_type()
? absl::make_optional<envoy::config::cluster::v3::Cluster::CustomClusterType>(
Expand Down
4 changes: 4 additions & 0 deletions source/common/upstream/upstream_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,9 @@ class ClusterInfoImpl : public ClusterInfo,
return connection_pool_per_downstream_connection_;
}
bool warmHosts() const override { return warm_hosts_; }
bool setLocalInterfaceNameOnUpstreamConnections() const override {
return set_local_interface_name_on_upstream_connections_;
}
const absl::optional<envoy::config::core::v3::UpstreamHttpProtocolOptions>&
upstreamHttpProtocolOptions() const override {
return http_protocol_options_->upstream_http_protocol_options_;
Expand Down Expand Up @@ -821,6 +824,7 @@ class ClusterInfoImpl : public ClusterInfo,
const bool drain_connections_on_host_removal_;
const bool connection_pool_per_downstream_connection_;
const bool warm_hosts_;
const bool set_local_interface_name_on_upstream_connections_;
const absl::optional<envoy::config::core::v3::UpstreamHttpProtocolOptions>
upstream_http_protocol_options_;
absl::optional<std::string> eds_service_name_;
Expand Down
5 changes: 4 additions & 1 deletion source/server/api_listener_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ class ApiListenerImplBase : public ApiListener,
IS_ENVOY_BUG("Unexpected function call");
}
bool readEnabled() const override { return true; }
const Network::ConnectionInfoSetter& connectionInfoProvider() const override {
Network::ConnectionInfoSetter& connectionInfoProvider() override {
return *connection_info_provider_;
}
const Network::ConnectionInfoProvider& connectionInfoProvider() const override {
return *connection_info_provider_;
}
Network::ConnectionInfoProviderSharedPtr connectionInfoProviderSharedPtr() const override {
Expand Down
2 changes: 1 addition & 1 deletion test/common/network/happy_eyeballs_connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ TEST_F(HappyEyeballsConnectionImplTest, NextProtocol) {
TEST_F(HappyEyeballsConnectionImplTest, AddressProvider) {
connectFirstAttempt();

const ConnectionInfoSetterImpl provider(std::make_shared<Address::Ipv4Instance>(80),
ConnectionInfoSetterImpl provider(std::make_shared<Address::Ipv4Instance>(80),
std::make_shared<Address::Ipv4Instance>(80));
EXPECT_CALL(*created_connections_[0], connectionInfoProvider()).WillOnce(ReturnRef(provider));
impl_->connectionInfoProvider();
Expand Down
8 changes: 8 additions & 0 deletions test/integration/filters/stream_info_to_headers_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,17 @@ class StreamInfoToHeadersFilter : public Http::PassThroughFilter {
Http::LowerCaseString("alpn"),
decoder_callbacks_->streamInfo().upstreamInfo()->upstreamSslConnection()->alpn());
}

headers.addCopy(Http::LowerCaseString("num_streams"),
decoder_callbacks_->streamInfo().upstreamInfo()->upstreamNumStreams());

const auto maybe_local_interface_name =
decoder_callbacks_->streamInfo().upstreamInfo()->upstreamInterfaceName();
if (maybe_local_interface_name.has_value()) {
headers.addCopy(Http::LowerCaseString("local_interface_name"),
maybe_local_interface_name.value());
}

StreamInfo::UpstreamTiming& upstream_timing =
decoder_callbacks_->streamInfo().upstreamInfo()->upstreamTiming();
if (upstream_timing.upstream_connect_start_.has_value()) {
Expand Down
57 changes: 57 additions & 0 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3508,6 +3508,63 @@ TEST_P(ProtocolIntegrationTest, HandleUpstreamSocketFail) {
test_server_.reset();
}

TEST_P(ProtocolIntegrationTest, NoLocalInterfaceNameForUpstreamConnection) {
config_helper_.prependFilter(R"EOF(
name: stream-info-to-headers-filter
typed_config:
"@type": type.googleapis.com/google.protobuf.Empty
)EOF");
initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));

// Send a headers only request.
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
waitForNextUpstreamRequest();

// Make sure that the body was injected to the request.
EXPECT_TRUE(upstream_request_->complete());

// Send a headers only response.
upstream_request_->encodeHeaders(default_response_headers_, true);
ASSERT_TRUE(response->waitForEndStream());

// Make sure that the local interface name was not populated. This is the runtime default.
EXPECT_TRUE(response->headers().get(Http::LowerCaseString("local_interface_name")).empty());
}

TEST_P(ProtocolIntegrationTest, LocalInterfaceNameForUpstreamConnection) {
config_helper_.prependFilter(R"EOF(
name: stream-info-to-headers-filter
typed_config:
"@type": type.googleapis.com/google.protobuf.Empty
)EOF");

config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
bootstrap.mutable_static_resources()
->mutable_clusters(0)
->mutable_upstream_connection_options()
->set_set_local_interface_name_on_upstream_connections(true);
});
initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));

// Send a headers only request.
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
waitForNextUpstreamRequest();

// Make sure that the body was injected to the request.
EXPECT_TRUE(upstream_request_->complete());

// Send a headers only response.
upstream_request_->encodeHeaders(default_response_headers_, true);
ASSERT_TRUE(response->waitForEndStream());

// Make sure that the local interface name was populated due to runtime override.
EXPECT_FALSE(response->headers().get(Http::LowerCaseString("local_interface_name")).empty());
}

#ifdef NDEBUG
// These tests send invalid request and response header names which violate ASSERT while creating
// such request/response headers. So they can only be run in NDEBUG mode.
Expand Down
1 change: 1 addition & 0 deletions test/mocks/network/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class MockConnectionBase {
MOCK_METHOD(void, readDisable, (bool disable)); \
MOCK_METHOD(void, detectEarlyCloseWhenReadDisabled, (bool)); \
MOCK_METHOD(bool, readEnabled, (), (const)); \
MOCK_METHOD(ConnectionInfoSetter&, connectionInfoProvider, ()); \
MOCK_METHOD(const ConnectionInfoProvider&, connectionInfoProvider, (), (const)); \
MOCK_METHOD(ConnectionInfoProviderSharedPtr, connectionInfoProviderSharedPtr, (), (const)); \
MOCK_METHOD(absl::optional<Connection::UnixDomainSocketPeerCredentials>, \
Expand Down
1 change: 1 addition & 0 deletions test/mocks/upstream/cluster_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class MockClusterInfo : public ClusterInfo {
MOCK_METHOD(bool, drainConnectionsOnHostRemoval, (), (const));
MOCK_METHOD(bool, connectionPoolPerDownstreamConnection, (), (const));
MOCK_METHOD(bool, warmHosts, (), (const));
MOCK_METHOD(bool, setLocalInterfaceNameOnUpstreamConnections, (), (const));
MOCK_METHOD(const absl::optional<envoy::config::core::v3::UpstreamHttpProtocolOptions>&,
upstreamHttpProtocolOptions, (), (const));
MOCK_METHOD(const absl::optional<envoy::config::core::v3::AlternateProtocolsCacheOptions>&,
Expand Down