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 doc/admin-guide/files/sni.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ tunnel_route Inbound Destination as an FQDN and port, separated b
Protocol <proxy-protocol>` for more information on Proxy Protocol and how it is
configured for |TS|.

Note that only one of the ``{inbound_local_port}`` and ``{proxy_protocol_port}`` literal strings can be specified. The match group number can be used in combination with either one of those.

Comment thread
lzx404243 marked this conversation as resolved.
For each of these tunnel targets, unless the port is explicitly specified in the target
(e.g., if the port is derived from the Proxy Protocol header), the port must be
specified in the :ts:cv:`proxy.config.http.connect_ports` configuration in order for
Expand Down
44 changes: 32 additions & 12 deletions iocore/net/P_SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ class TunnelDestination : public ActionItem
// ID of the configured variable. This will be used to know which function
// should be called when processing the tunnel destination.
enum OpId : int32_t {
DEFAULT = -1, // No specific variable set.
MATCH_GROUPS, // Deal with configured groups.
MAP_WITH_RECV_PORT, // Use port from inbound local
MAP_WITH_PROXY_PROTOCOL_PORT, // Use port from the proxy protocol
Expand All @@ -109,12 +108,26 @@ class TunnelDestination : public ActionItem
const std::vector<int> &alpn)
: destination(dest), type(type), tunnel_prewarm(prewarm), alpn_ids(alpn)
{
// Check for port variable specification. Note that this is checked before
// the match group so that the corresponding function can be applied before
// the match group expansion(when the var_start_pos is still accurate).
auto recv_port_start_pos = destination.find(MAP_WITH_RECV_PORT_STR);
auto pp_port_start_pos = destination.find(MAP_WITH_PROXY_PROTOCOL_PORT_STR);
bool has_recv_port_var = recv_port_start_pos != std::string::npos;
bool has_pp_port_var = pp_port_start_pos != std::string::npos;
if (has_recv_port_var && has_pp_port_var) {
Error("Invalid destination \"%.*s\" in SNI configuration - Only one port variable can be specified.",
static_cast<int>(destination.size()), destination.data());
} else if (has_recv_port_var) {
fnArrIndexes.push_back(OpId::MAP_WITH_RECV_PORT);
var_start_pos = recv_port_start_pos;
} else if (has_pp_port_var) {
fnArrIndexes.push_back(OpId::MAP_WITH_PROXY_PROTOCOL_PORT);
var_start_pos = pp_port_start_pos;
}
// Check for match groups as well.
if (destination.find_first_of('$') != std::string::npos) {
fnArrIndex = OpId::MATCH_GROUPS;
} else if (var_start_pos = destination.find(MAP_WITH_RECV_PORT_STR); var_start_pos != std::string::npos) {
fnArrIndex = OpId::MAP_WITH_RECV_PORT;
} else if (var_start_pos = destination.find(MAP_WITH_PROXY_PROTOCOL_PORT_STR); var_start_pos != std::string::npos) {
fnArrIndex = OpId::MAP_WITH_PROXY_PROTOCOL_PORT;
fnArrIndexes.push_back(OpId::MATCH_GROUPS);
}
}
~TunnelDestination() override {}
Expand All @@ -126,13 +139,17 @@ class TunnelDestination : public ActionItem
SSLNetVConnection *ssl_netvc = dynamic_cast<SSLNetVConnection *>(snis);
const char *servername = snis->get_sni_server_name();
if (ssl_netvc) {
if (fnArrIndex == OpId::DEFAULT) {
if (fnArrIndexes.empty()) {
ssl_netvc->set_tunnel_destination(destination, type, !TLSTunnelSupport::PORT_IS_DYNAMIC, tunnel_prewarm);
Debug("ssl_sni", "Destination now is [%s], fqdn [%s]", destination.c_str(), servername);
} else {
// Dispatch to the correct tunnel destination port function.
bool port_is_dynamic = false;
const auto &fixed_dst = fix_destination[fnArrIndex](destination, var_start_pos, ctx, ssl_netvc, port_is_dynamic);
bool port_is_dynamic = false;
auto fixed_dst{destination};
// Apply mapping functions to get the final destination.
for (auto fnArrIndex : fnArrIndexes) {
// Dispatch to the correct tunnel destination port function.
fixed_dst = fix_destination[fnArrIndex](fixed_dst, var_start_pos, ctx, ssl_netvc, port_is_dynamic);
}
ssl_netvc->set_tunnel_destination(fixed_dst, type, port_is_dynamic, tunnel_prewarm);
Debug("ssl_sni", "Destination now is [%s], configured [%s], fqdn [%s]", fixed_dst.c_str(), destination.c_str(), servername);
}
Expand Down Expand Up @@ -232,8 +249,11 @@ class TunnelDestination : public ActionItem
YamlSNIConfig::TunnelPreWarm tunnel_prewarm = YamlSNIConfig::TunnelPreWarm::UNSET;
const std::vector<int> &alpn_ids;

OpId fnArrIndex{OpId::DEFAULT}; /// On creation, we decide which function needs to be called, set the index and then we
/// call it with the relevant data
/** The indexes of the mapping functions that need to be called. On
creation, we decide which functions need to be called, add the coressponding
indexes and then we call those functions with the relevant data.
*/
std::vector<OpId> fnArrIndexes;

/// tunnel_route destination callback array.
static std::array<std::function<std::string(std::string_view, // destination view
Expand Down
39 changes: 39 additions & 0 deletions tests/gold_tests/tls/tls_tunnel.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
dns.addRecords(records={"two.example.one": ["127.0.0.1"]})
dns.addRecords(records={"backend.incoming.port.com": ["127.0.0.1"]})
dns.addRecords(records={"backend.proxy.protocol.port.com": ["127.0.0.1"]})
dns.addRecords(records={"backend.wildcard.with.incoming.port.com": ["127.0.0.1"]})
dns.addRecords(records={"backend.wildcard.with.proxy.protocol.port.com": ["127.0.0.1"]})
# Need no remap rules. Everything should be processed by sni

# Make sure the TS server certs are different from the origin certs
Expand Down Expand Up @@ -103,6 +105,10 @@
" tunnel_route: backend.incoming.port.com:{inbound_local_port}",
"- fqdn: 'proxy.protocol.port.com'",
" tunnel_route: backend.proxy.protocol.port.com:{proxy_protocol_port}",
"- fqdn: '*.*.incoming.port.com'",
" tunnel_route: backend.$1.$2.incoming.port.com:{inbound_local_port}",
"- fqdn: '*.*.proxy.protocol.port.com'",
" tunnel_route: backend.$1.$2.proxy.protocol.port.com:{proxy_protocol_port}",
])

tr = Test.AddTestRun("foo.com Tunnel-test")
Expand Down Expand Up @@ -236,6 +242,39 @@
f"Rejected a tunnel to port {rejected_port} not in connect_ports",
"Verify the tunnel was rejected")

tr = Test.AddTestRun("test wildcard with inbound_local_port")
tr.Processes.Default.Command = "curl -vvv --resolve 'wildcard.with.incoming.port.com:{0}:127.0.0.1' -k https://wildcard.with.incoming.port.com:{0}".format(
ts.Variables.ssl_port)

# See the inbound_local_port test above for the explanation of the return code.
tr.ReturnCode = 35
tr.StillRunningAfter = ts
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
rf"Destination now is \[backend.wildcard.with.incoming.port.com:{ts.Variables.ssl_port}\]",
"Verify the tunnel destination is expanded correctly.")
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
f"CONNECT tunnel://backend.wildcard.with.incoming.port.com:{ts.Variables.ssl_port} HTTP/1.1",
"Verify a CONNECT request is handled")
ts.Disk.traffic_out.Content += Testers.ContainsExpression("HTTP/1.1 400 Direct self loop detected", "The loop should be detected")

tr = Test.AddTestRun("test wildcard with proxy_protocol_port")
tr.Setup.Copy('proxy_protocol_client.py')
tr.Processes.Default.Command = (
f'{sys.executable} proxy_protocol_client.py '
f'127.0.0.1 {ts.Variables.proxy_protocol_ssl_port} wildcard.with.proxy.protocol.port.com '
f'127.0.0.1 127.0.0.1 60123 {server_foo.Variables.SSL_Port} '
f'2 --https'
)
tr.ReturnCode = 0
tr.TimeOut = 5
tr.StillRunningAfter = ts
ts.Disk.traffic_out.Content += Testers.ContainsExpression(
rf"Destination now is \[backend.wildcard.with.proxy.protocol.port.com:{server_foo.Variables.SSL_Port}\]",
"Verify the tunnel destination is expanded correctly.")
tr.Processes.Default.Streams.All += Testers.ContainsExpression(
"HTTP/1.1 200 OK",
"Verify a successful response is received")

# Update sni file and reload
tr = Test.AddTestRun("Update config files")
# Update the SNI config
Expand Down