Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
/*/extensions/filters/http/header_to_metadata @rgs1 @zuercher
# alts transport socket extension
/*/extensions/transport_sockets/alts @lizan @yangminzhu
# sni_cluster extension
/*/extensions/filters/network/sni_cluster @rshriram @lizan
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ filters.
redis_proxy_filter
tcp_proxy_filter
thrift_proxy_filter
sni_cluster_filter
13 changes: 13 additions & 0 deletions docs/root/configuration/network_filters/sni_cluster_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. _config_network_filters_sni_cluster:

Upstream Cluster from SNI
=========================

The `sni_cluster` is a network filter that uses the SNI value in a TLS
connection as the upstream cluster name. The filter will not modify the
upstream cluster for non-TLS connections.

This filter has no configuration. It must be installed before the
:ref:`tcp_proxy <config_network_filters_tcp_proxy>` filter.

* :ref:`v2 API reference <envoy_api_field_listener.Filter.name>`
10 changes: 10 additions & 0 deletions docs/root/configuration/network_filters/tcp_proxy_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ TCP proxy
* :ref:`v1 API reference <config_network_filters_tcp_proxy_v1>`
* :ref:`v2 API reference <envoy_api_msg_config.filter.network.tcp_proxy.v2.TcpProxy>`

.. _config_network_filters_tcp_proxy_dynamic_cluster:

Dynamic cluster selection
-------------------------

The upstream cluster used by the TCP proxy filter can be dynamically set by
other network filters on a per-connection basis by setting a per-connection
state object under the key `envoy.tcp_proxy.cluster`. See the
implementation for the details.

.. _config_network_filters_tcp_proxy_stats:

Statistics
Expand Down
2 changes: 2 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Version history
* config: Fixed stat inconsistency between xDS and ADS implementation. :ref:`update_failure <config_cluster_manager_cds>`
stat is incremented in case of network failure and :ref:`update_rejected <config_cluster_manager_cds>` stat is incremented
in case of schema/validation error.
* :ref:`sni_cluster <config_network_filters_sni_cluster>`: introduced a new network filter that forwards connections to the
upstream cluster specified by the SNI value presented by the client during a TLS handshake.
* config: Added a stat :ref:`connected_state <management_server_stats>` that indicates current connected state of Envoy with
management server.

Expand Down
1 change: 1 addition & 0 deletions source/common/tcp_proxy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ envoy_cc_library(
"//include/envoy/event:dispatcher_interface",
"//include/envoy/network:connection_interface",
"//include/envoy/network:filter_interface",
"//include/envoy/request_info:filter_state_interface",
"//include/envoy/router:router_interface",
"//include/envoy/server:filter_config_interface",
"//include/envoy/stats:stats_interface",
Expand Down
9 changes: 9 additions & 0 deletions source/common/tcp_proxy/tcp_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
namespace Envoy {
namespace TcpProxy {

const std::string PerConnectionCluster::Key = "envoy.tcp_proxy.cluster";

Config::Route::Route(
const envoy::config::filter::network::tcp_proxy::v2::TcpProxy::DeprecatedV1::TCPRoute& config) {
cluster_name_ = config.cluster();
Expand Down Expand Up @@ -108,6 +110,13 @@ Config::Config(const envoy::config::filter::network::tcp_proxy::v2::TcpProxy& co
}

const std::string& Config::getRegularRouteFromEntries(Network::Connection& connection) {
// First check if the per-connection state to see if we need to route to a pre-selected cluster
if (connection.perConnectionState().hasData<PerConnectionCluster>(PerConnectionCluster::Key)) {
const PerConnectionCluster& per_connection_cluster =
connection.perConnectionState().getData<PerConnectionCluster>(PerConnectionCluster::Key);
return per_connection_cluster.value();
}

for (const Config::Route& route : routes_) {
if (!route.source_port_ranges_.empty() &&
!Network::Utility::portInRangeList(*connection.remoteAddress(),
Expand Down
14 changes: 14 additions & 0 deletions source/common/tcp_proxy/tcp_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "envoy/event/timer.h"
#include "envoy/network/connection.h"
#include "envoy/network/filter.h"
#include "envoy/request_info/filter_state.h"
#include "envoy/runtime/runtime.h"
#include "envoy/server/filter_config.h"
#include "envoy/stats/scope.h"
Expand Down Expand Up @@ -154,6 +155,19 @@ class Config {

typedef std::shared_ptr<Config> ConfigSharedPtr;

/**
* Per-connection TCP Proxy Cluster configuration.
*/
class PerConnectionCluster : public RequestInfo::FilterState::Object {
public:
PerConnectionCluster(absl::string_view cluster) : cluster_(cluster) {}
const std::string& value() const { return cluster_; }
static const std::string Key;

private:
const std::string cluster_;
};

/**
* An implementation of a TCP (L3/L4) proxy. This filter will instantiate a new outgoing TCP
* connection using the defined load balancing proxy for the configured cluster. All data will
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ EXTENSIONS = {
"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config",
"envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config",
"envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config",
"envoy.filters.network.sni_cluster": "//source/extensions/filters/network/sni_cluster:config",

#
# Resource monitors
Expand Down Expand Up @@ -181,6 +182,7 @@ WINDOWS_EXTENSIONS = {
#"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config",
"envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config",
#"envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config",
#"envoy.filters.network.sni_cluster": "//source/extensions/filters/network/sni_cluster:config",

#
# Stat sinks
Expand Down
34 changes: 34 additions & 0 deletions source/extensions/filters/network/sni_cluster/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

envoy_package()

envoy_cc_library(
name = "sni_cluster",
srcs = ["sni_cluster.cc"],
hdrs = ["sni_cluster.h"],
deps = [
"//include/envoy/network:connection_interface",
"//include/envoy/network:filter_interface",
"//source/common/common:assert_lib",
"//source/common/common:minimal_logger_lib",
"//source/common/tcp_proxy",
],
)

envoy_cc_library(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
deps = [
":sni_cluster",
"//include/envoy/registry",
"//include/envoy/server:filter_config_interface",
"//source/extensions/filters/network:well_known_names",
],
)
41 changes: 41 additions & 0 deletions source/extensions/filters/network/sni_cluster/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "extensions/filters/network/sni_cluster/config.h"

#include "envoy/registry/registry.h"
#include "envoy/server/filter_config.h"

#include "extensions/filters/network/sni_cluster/sni_cluster.h"

namespace Envoy {
namespace Extensions {
namespace NetworkFilters {
namespace SniCluster {

Network::FilterFactoryCb
SniClusterNetworkFilterConfigFactory::createFilterFactory(const Json::Object&,
Server::Configuration::FactoryContext&) {
// Only used in v1 filters.
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since v1 will be completely removed soon, should we mark this method non-PURE and in NamedNetworkFilterConfigFactory and make it NOT_IMPLEMENTED?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was waiting for the 1.8 cycle. after that, we could remove these methods entirely from everywhere.

}

Network::FilterFactoryCb SniClusterNetworkFilterConfigFactory::createFilterFactoryFromProto(
const Protobuf::Message&, Server::Configuration::FactoryContext&) {
return [](Network::FilterManager& filter_manager) -> void {
filter_manager.addReadFilter(std::make_shared<SniClusterFilter>());
};
}

ProtobufTypes::MessagePtr SniClusterNetworkFilterConfigFactory::createEmptyConfigProto() {
return std::make_unique<ProtobufWkt::Empty>();
}

/**
* Static registration for the sni_cluster filter. @see RegisterFactory.
*/
static Registry::RegisterFactory<SniClusterNetworkFilterConfigFactory,
Server::Configuration::NamedNetworkFilterConfigFactory>
registered_;

} // namespace SniCluster
} // namespace NetworkFilters
} // namespace Extensions
} // namespace Envoy
31 changes: 31 additions & 0 deletions source/extensions/filters/network/sni_cluster/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include "envoy/server/filter_config.h"

#include "extensions/filters/network/well_known_names.h"

namespace Envoy {
namespace Extensions {
namespace NetworkFilters {
namespace SniCluster {

/**
* Config registration for the sni_cluster filter. @see NamedNetworkFilterConfigFactory.
*/
class SniClusterNetworkFilterConfigFactory
: public Server::Configuration::NamedNetworkFilterConfigFactory {
public:
// NamedNetworkFilterConfigFactory
Network::FilterFactoryCb createFilterFactory(const Json::Object&,
Server::Configuration::FactoryContext&) override;
Network::FilterFactoryCb
createFilterFactoryFromProto(const Protobuf::Message&,
Server::Configuration::FactoryContext&) override;
ProtobufTypes::MessagePtr createEmptyConfigProto() override;
std::string name() override { return NetworkFilterNames::get().SniCluster; }
};

} // namespace SniCluster
} // namespace NetworkFilters
} // namespace Extensions
} // namespace Envoy
30 changes: 30 additions & 0 deletions source/extensions/filters/network/sni_cluster/sni_cluster.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "extensions/filters/network/sni_cluster/sni_cluster.h"

#include "envoy/network/connection.h"

#include "common/common/assert.h"
#include "common/tcp_proxy/tcp_proxy.h"

namespace Envoy {
namespace Extensions {
namespace NetworkFilters {
namespace SniCluster {

Network::FilterStatus SniClusterFilter::onNewConnection() {
absl::string_view sni = read_callbacks_->connection().requestedServerName();
ENVOY_CONN_LOG(trace, "sni_cluster: new connection with server name {}",
read_callbacks_->connection(), sni);

if (!sni.empty()) {
// Set the tcp_proxy cluster to the same value as SNI
read_callbacks_->connection().perConnectionState().setData(
TcpProxy::PerConnectionCluster::Key, std::make_unique<TcpProxy::PerConnectionCluster>(sni));
}

return Network::FilterStatus::Continue;
}

} // namespace SniCluster
} // namespace NetworkFilters
} // namespace Extensions
} // namespace Envoy
34 changes: 34 additions & 0 deletions source/extensions/filters/network/sni_cluster/sni_cluster.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "envoy/network/filter.h"

#include "common/common/logger.h"

namespace Envoy {
namespace Extensions {
namespace NetworkFilters {
namespace SniCluster {

/**
* Implementation of the sni_cluster filter that sets the upstream cluster name from
* the SNI field in the TLS connection.
*/
class SniClusterFilter : public Network::ReadFilter, Logger::Loggable<Logger::Id::filter> {
public:
// Network::ReadFilter
Network::FilterStatus onData(Buffer::Instance&, bool) override {
return Network::FilterStatus::Continue;
}
Network::FilterStatus onNewConnection() override;
void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override {
read_callbacks_ = &callbacks;
}

private:
Network::ReadFilterCallbacks* read_callbacks_{};
};

} // namespace SniCluster
} // namespace NetworkFilters
} // namespace Extensions
} // namespace Envoy
2 changes: 2 additions & 0 deletions source/extensions/filters/network/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class NetworkFilterNameValues {
const std::string ThriftProxy = "envoy.filters.network.thrift_proxy";
// Role based access control filter
const std::string Rbac = "envoy.filters.network.rbac";
// SNI Cluster filter
const std::string SniCluster = "envoy.filters.network.sni_cluster";

// Converts names from v1 to v2
const Config::V1Converter v1_converter_;
Expand Down
19 changes: 19 additions & 0 deletions test/common/tcp_proxy/tcp_proxy_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1142,5 +1142,24 @@ TEST_F(TcpProxyRoutingTest, RoutableConnection) {
EXPECT_EQ(non_routable_cx, config_->stats().downstream_cx_no_route_.value());
}

// Test that the tcp proxy uses the cluster from FilterState if set
TEST_F(TcpProxyRoutingTest, UseClusterFromPerConnectionCluster) {
setup();

RequestInfo::FilterStateImpl per_connection_state;
per_connection_state.setData("envoy.tcp_proxy.cluster",
std::make_unique<PerConnectionCluster>("filter_state_cluster"));
ON_CALL(connection_, perConnectionState()).WillByDefault(ReturnRef(per_connection_state));
EXPECT_CALL(Const(connection_), perConnectionState())
.WillRepeatedly(ReturnRef(per_connection_state));

// Expect filter to try to open a connection to specified cluster.
EXPECT_CALL(factory_context_.cluster_manager_,
tcpConnPoolForCluster("filter_state_cluster", _, _))
.WillOnce(Return(nullptr));

filter_->onNewConnection();
}

} // namespace TcpProxy
} // namespace Envoy
25 changes: 25 additions & 0 deletions test/extensions/filters/network/sni_cluster/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_test",
"envoy_package",
)
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
)

envoy_package()

envoy_extension_cc_test(
name = "sni_cluster_test",
srcs = ["sni_cluster_test.cc"],
extension_name = "envoy.filters.network.sni_cluster",
deps = [
"//source/extensions/filters/network/sni_cluster",
"//source/extensions/filters/network/sni_cluster:config",
"//test/mocks/network:network_mocks",
"//test/mocks/server:server_mocks",
],
)
Loading