Skip to content
Draft
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 example/plugins/c-api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
63 changes: 63 additions & 0 deletions example/plugins/c-api/sni_action/sni_action.cc
Original file line number Diff line number Diff line change
@@ -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 <ts/ts.h>

#include <openssl/ssl.h>

#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<char *>(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<char *>(argv[1]);
return TS_SUCCESS;
}

TSReturnCode
TSSNIInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
{
return TS_SUCCESS;
}
62 changes: 32 additions & 30 deletions include/iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -91,36 +92,37 @@ struct YamlSNIConfig {

std::vector<ts::port_range_t> inbound_port_ranges;

std::optional<bool> offer_h2; // Has no value by default, so do not initialize!
std::optional<bool> 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<int> tunnel_alpn{};
std::optional<int> http2_buffer_water_mark;
std::optional<int> http2_max_settings_frames_per_minute;
std::optional<int> http2_max_ping_frames_per_minute;
std::optional<int> http2_max_priority_frames_per_minute;
std::optional<int> http2_max_rst_stream_frames_per_minute;
std::optional<int> http2_max_continuation_frames_per_minute;
uint32_t server_max_early_data = 0;
std::optional<int> http2_initial_window_size_in;
std::optional<bool> offer_h2; // Has no value by default, so do not initialize!
std::optional<bool> 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<int> tunnel_alpn{};
std::optional<int> http2_buffer_water_mark;
std::optional<int> http2_max_settings_frames_per_minute;
std::optional<int> http2_max_ping_frames_per_minute;
std::optional<int> http2_max_priority_frames_per_minute;
std::optional<int> http2_max_rst_stream_frames_per_minute;
std::optional<int> http2_max_continuation_frames_per_minute;
uint32_t server_max_early_data = 0;
std::optional<int> http2_initial_window_size_in;
std::vector<std::tuple<std::string, std::string>> plugins;

bool tunnel_prewarm_srv = false;
uint32_t tunnel_prewarm_min = 0;
Expand Down
4 changes: 4 additions & 0 deletions include/ts/apidefs.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions src/iocore/net/SNIActionPerformer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -524,3 +526,20 @@ ServerGroupsList::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) cons
}
return SSL_TLSEXT_ERR_OK;
}

SNIPlugins::SNIPlugins(std::vector<std::tuple<std::string, std::string>> 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;
}
54 changes: 54 additions & 0 deletions src/iocore/net/SNIActionPerformer.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

#pragma once

#include <dlfcn.h>

#include "iocore/net/SNIActionItem.h"
#include "iocore/net/SSLTypes.h"
#include "iocore/net/YamlSNIConfig.h"
Expand Down Expand Up @@ -357,3 +359,55 @@ class ServerGroupsList : public ActionItem
private:
std::string const server_groups_list{};
};

class SNIPlugins : public ActionItem
{
public:
SNIPlugins(std::vector<std::tuple<std::string, std::string>> 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<int (*)()>(init)();
} else {
return;
}
}

void *newInstance = dlsym(_handle, "TSSNINewInstance");
_callback = reinterpret_cast<CallbackFunction>(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<int (*)(int, const char **, void **)>(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<SNIPlugin> _plugins;
};
18 changes: 17 additions & 1 deletion src/iocore/net/YamlSNIConfig.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTTP2MaxContinuationFramesPerMinute>(http2_max_continuation_frames_per_minute.value()));
}
if (!plugins.empty()) {
actions.push_back(std::make_unique<SNIPlugins>(plugins));
}

actions.push_back(std::make_unique<ServerMaxEarlyData>(server_max_early_data));
actions.push_back(std::make_unique<SNI_IpAllow>(ip_allow, fqdn));
Expand Down Expand Up @@ -246,7 +249,8 @@ std::set<std::string> 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
{
Expand Down Expand Up @@ -488,6 +492,18 @@ template <> struct convert<YamlSNIConfig::Item> {
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<std::string>();
auto params = p["parameters"].as<std::string>();
item.plugins.push_back({path, params});
}
}

return true;
}

Expand Down