From 267fff9bfb8fbe63f2aa5f4690742ee756bc5bc3 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 11 Nov 2025 16:18:42 -0700 Subject: [PATCH] Add support for SNI plugin --- example/plugins/c-api/CMakeLists.txt | 2 + .../plugins/c-api/sni_action/sni_action.cc | 63 +++++++++++++++++++ include/iocore/net/YamlSNIConfig.h | 62 +++++++++--------- include/ts/apidefs.h.in | 4 ++ src/iocore/net/SNIActionPerformer.cc | 19 ++++++ src/iocore/net/SNIActionPerformer.h | 54 ++++++++++++++++ src/iocore/net/YamlSNIConfig.cc | 18 +++++- 7 files changed, 191 insertions(+), 31 deletions(-) create mode 100644 example/plugins/c-api/sni_action/sni_action.cc diff --git a/example/plugins/c-api/CMakeLists.txt b/example/plugins/c-api/CMakeLists.txt index 678fe416dde..b2a7a605dd6 100644 --- a/example/plugins/c-api/CMakeLists.txt +++ b/example/plugins/c-api/CMakeLists.txt @@ -30,6 +30,8 @@ add_atsplugin(bnull_transform ./bnull_transform/bnull_transform.cc) add_atsplugin(replace_header ./replace_header/replace_header.cc) add_atsplugin(ssl_sni ./ssl_sni/ssl_sni.cc) target_link_libraries(ssl_sni PRIVATE OpenSSL::SSL) +add_atsplugin(sni_action ./sni_action/sni_action.cc) +target_link_libraries(sni_action PRIVATE OpenSSL::SSL) add_atsplugin(passthru ./passthru/passthru.cc) add_atsplugin(response_header_1 ./response_header_1/response_header_1.cc) add_atsplugin(denylist_1 ./denylist_1/denylist_1.cc) diff --git a/example/plugins/c-api/sni_action/sni_action.cc b/example/plugins/c-api/sni_action/sni_action.cc new file mode 100644 index 00000000000..9a5c61055a6 --- /dev/null +++ b/example/plugins/c-api/sni_action/sni_action.cc @@ -0,0 +1,63 @@ +/** @file + + SSL SNI Action plugin. + + Demonstrates an SNI Action that is implemented by a plugin + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include + +#include + +#define PLUGIN_NAME "sni_action" +#define PCP "[" PLUGIN_NAME "] " + +namespace +{ +DbgCtl dbg_ctl{PLUGIN_NAME}; +} // namespace + +int +TSSNIDoAction(void *ih, SSL * /* ssl */) +{ + Dbg(dbg_ctl, "params: %s", static_cast(ih)); + + // Randomly causes handshake failrue + int random = rand(); + if (random % 5 == 0) { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } else { + return SSL_TLSEXT_ERR_OK; + } +} + +TSReturnCode +TSSNINewInstance(int /* argc */, const char *argv[], void **ih) +{ + *ih = const_cast(argv[1]); + return TS_SUCCESS; +} + +TSReturnCode +TSSNIInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */) +{ + return TS_SUCCESS; +} diff --git a/include/iocore/net/YamlSNIConfig.h b/include/iocore/net/YamlSNIConfig.h index c8396a76e74..7ef075f9e07 100644 --- a/include/iocore/net/YamlSNIConfig.h +++ b/include/iocore/net/YamlSNIConfig.h @@ -74,6 +74,7 @@ TSDECL(quic); TSDECL(host_sni_policy); TSDECL(http2_initial_window_size_in); TSDECL(server_max_early_data); +TSDECL(plugins); #undef TSDECL class ActionItem; @@ -91,36 +92,37 @@ struct YamlSNIConfig { std::vector inbound_port_ranges; - std::optional offer_h2; // Has no value by default, so do not initialize! - std::optional offer_quic; // Has no value by default, so do not initialize! - uint8_t verify_client_level = 255; - std::string verify_client_ca_file; - std::string verify_client_ca_dir; - uint8_t host_sni_policy = 255; - SNIRoutingType tunnel_type = SNIRoutingType::NONE; - std::string tunnel_destination; - Policy verify_server_policy = Policy::UNSET; - Property verify_server_properties = Property::UNSET; - std::string client_cert; - std::string client_key; - std::string client_sni_policy; - std::string server_cipher_suite; - std::string server_TLSv1_3_cipher_suites; - std::string server_groups_list; - std::string ip_allow; - bool protocol_unset = true; - unsigned long protocol_mask; - int valid_tls_version_min_in = -1; - int valid_tls_version_max_in = -1; - std::vector tunnel_alpn{}; - std::optional http2_buffer_water_mark; - std::optional http2_max_settings_frames_per_minute; - std::optional http2_max_ping_frames_per_minute; - std::optional http2_max_priority_frames_per_minute; - std::optional http2_max_rst_stream_frames_per_minute; - std::optional http2_max_continuation_frames_per_minute; - uint32_t server_max_early_data = 0; - std::optional http2_initial_window_size_in; + std::optional offer_h2; // Has no value by default, so do not initialize! + std::optional offer_quic; // Has no value by default, so do not initialize! + uint8_t verify_client_level = 255; + std::string verify_client_ca_file; + std::string verify_client_ca_dir; + uint8_t host_sni_policy = 255; + SNIRoutingType tunnel_type = SNIRoutingType::NONE; + std::string tunnel_destination; + Policy verify_server_policy = Policy::UNSET; + Property verify_server_properties = Property::UNSET; + std::string client_cert; + std::string client_key; + std::string client_sni_policy; + std::string server_cipher_suite; + std::string server_TLSv1_3_cipher_suites; + std::string server_groups_list; + std::string ip_allow; + bool protocol_unset = true; + unsigned long protocol_mask; + int valid_tls_version_min_in = -1; + int valid_tls_version_max_in = -1; + std::vector tunnel_alpn{}; + std::optional http2_buffer_water_mark; + std::optional http2_max_settings_frames_per_minute; + std::optional http2_max_ping_frames_per_minute; + std::optional http2_max_priority_frames_per_minute; + std::optional http2_max_rst_stream_frames_per_minute; + std::optional http2_max_continuation_frames_per_minute; + uint32_t server_max_early_data = 0; + std::optional http2_initial_window_size_in; + std::vector> plugins; bool tunnel_prewarm_srv = false; uint32_t tunnel_prewarm_min = 0; diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in index 0bc28141907..77e5b32ce79 100644 --- a/include/ts/apidefs.h.in +++ b/include/ts/apidefs.h.in @@ -1589,6 +1589,10 @@ struct TSResponseAction { */ extern "C" void TSPluginInit(int argc, const char *argv[]); +extern "C" TSReturnCode TSSNIInit(); +extern "C" TSReturnCode TSSNINewInstance(int argc, char *argv[], void **ih); +extern "C" int TSSNIDoAction(void *ih, TSSslConnection); + namespace ts { static const int NO_FD = -1; ///< No or invalid file descriptor. diff --git a/src/iocore/net/SNIActionPerformer.cc b/src/iocore/net/SNIActionPerformer.cc index 3b30db815df..e4c8beaa8bb 100644 --- a/src/iocore/net/SNIActionPerformer.cc +++ b/src/iocore/net/SNIActionPerformer.cc @@ -42,6 +42,8 @@ DbgCtl dbg_ctl_ssl_sni{"ssl_sni"}; } // end anonymous namespace +void *SNIPlugins::SNIPlugin::_handle = nullptr; + int ControlQUIC::SNIAction([[maybe_unused]] SSL &ssl, const Context & /* ctx ATS_UNUSED */) const { @@ -524,3 +526,20 @@ ServerGroupsList::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) cons } return SSL_TLSEXT_ERR_OK; } + +SNIPlugins::SNIPlugins(std::vector> plugins) +{ + for (auto [name, parameters] : plugins) { + Dbg(dbg_ctl_ssl_sni, "%s: %s", name.c_str(), parameters.c_str()); + _plugins.emplace_back(name, parameters); + } +} + +int +SNIPlugins::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const +{ + for (auto p : _plugins) { + p.invoke(&ssl); + } + return SSL_TLSEXT_ERR_OK; +} diff --git a/src/iocore/net/SNIActionPerformer.h b/src/iocore/net/SNIActionPerformer.h index c173caacaad..23c4e42d3fb 100644 --- a/src/iocore/net/SNIActionPerformer.h +++ b/src/iocore/net/SNIActionPerformer.h @@ -23,6 +23,8 @@ #pragma once +#include + #include "iocore/net/SNIActionItem.h" #include "iocore/net/SSLTypes.h" #include "iocore/net/YamlSNIConfig.h" @@ -357,3 +359,55 @@ class ServerGroupsList : public ActionItem private: std::string const server_groups_list{}; }; + +class SNIPlugins : public ActionItem +{ +public: + SNIPlugins(std::vector> plugins); + ~SNIPlugins() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + class SNIPlugin + { + public: + using CallbackFunction = int (*)(void *, SSL *); + SNIPlugin(std::string path, std::string parameters) + { + if (_handle == nullptr) { + _handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (void *init = dlsym(_handle, "TSSNIPluginInit"); init != nullptr) { + reinterpret_cast(init)(); + } else { + return; + } + } + + void *newInstance = dlsym(_handle, "TSSNINewInstance"); + _callback = reinterpret_cast(dlsym(_handle, "TSSNIDoAction")); + if (newInstance == nullptr || _callback == nullptr) { + return; + } + + // TODO Split the string seriously (there must be code for it somewhere) + int argc = 2; + const char *argv[] = {path.c_str(), parameters.c_str()}; + reinterpret_cast(newInstance)(argc, argv, &_instance); + } + ~SNIPlugin() { dlclose(_handle); } + int + invoke(SSL *ssl) + { + return _callback(_instance, ssl); + } + + private: + static void *_handle; + + void *_instance; + CallbackFunction _callback; + }; + + std::vector _plugins; +}; diff --git a/src/iocore/net/YamlSNIConfig.cc b/src/iocore/net/YamlSNIConfig.cc index bbc0eb4ace0..15c770dd8de 100644 --- a/src/iocore/net/YamlSNIConfig.cc +++ b/src/iocore/net/YamlSNIConfig.cc @@ -185,6 +185,9 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions) if (http2_max_continuation_frames_per_minute.has_value()) { actions.push_back(std::make_unique(http2_max_continuation_frames_per_minute.value())); } + if (!plugins.empty()) { + actions.push_back(std::make_unique(plugins)); + } actions.push_back(std::make_unique(server_max_early_data)); actions.push_back(std::make_unique(ip_allow, fqdn)); @@ -246,7 +249,8 @@ std::set valid_sni_config_keys = {TS_fqdn, TS_valid_tls_version_max_in, #endif TS_host_sni_policy, - TS_server_max_early_data}; + TS_server_max_early_data, + TS_plugins}; namespace YAML { @@ -488,6 +492,18 @@ template <> struct convert { item.server_max_early_data = SSLConfigParams::server_max_early_data; } + if (node[TS_plugins]) { + if (!node[TS_plugins].IsSequence()) { + throw YAML::ParserException(node.Mark(), "\"plugins\" is not sequence"); + } + + for (const auto &p : node[TS_plugins]) { + auto path = p["path"].as(); + auto params = p["parameters"].as(); + item.plugins.push_back({path, params}); + } + } + return true; }