This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new aff07ef3ab Adds a run-plugin operator to HRW (#11320)
aff07ef3ab is described below
commit aff07ef3ab25681d9a84539d25509709a4d17b5f
Author: Leif Hedstrom <[email protected]>
AuthorDate: Mon May 6 14:03:28 2024 -0600
Adds a run-plugin operator to HRW (#11320)
---
doc/admin-guide/plugins/header_rewrite.en.rst | 18 +++++++
plugins/header_rewrite/CMakeLists.txt | 4 +-
plugins/header_rewrite/factory.cc | 2 +
plugins/header_rewrite/header_rewrite.cc | 34 +++++++++----
plugins/header_rewrite/lulu.h | 7 ++-
plugins/header_rewrite/operator.h | 2 +
plugins/header_rewrite/operators.cc | 73 +++++++++++++++++++++++++++
plugins/header_rewrite/operators.h | 32 ++++++++++++
plugins/header_rewrite/parser.h | 22 ++++++--
9 files changed, 177 insertions(+), 17 deletions(-)
diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst
b/doc/admin-guide/plugins/header_rewrite.en.rst
index b7547dc37a..f8831e144a 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -775,6 +775,14 @@ changing the remapped destination, ``<part>`` should be
used to indicate the
component that is being modified (see `URL Parts`_). Currently the only valid
parts for rm-destination are QUERY, PATH, and PORT.
+run-plugin
+~~~~~~~~~~~~~~
+::
+
+ run-plugin <plugin-name>.so "<plugin-argument> ..."
+
+This allows to run an existing remap plugin, conditionally, from within a
+header rewrite rule.
set-header
~~~~~~~~~~
@@ -1350,3 +1358,13 @@ the client where the requested data was served from.::
cond %{HEADER:ATS-SRVR-UUID} ="" [OR]
cond %{CACHE} ="hit-fresh"
set-header ATS-SRVR-UUID %{ID:UNIQUE}
+
+Apply rate limiting for some select requests
+------------------------------------
+
+This rule will conditiionally, based on the client request headers, apply rate
+limiting to the request.::
+
+ cond %{REMAP_PSEUDO_HOOK} [AND]
+ cond %{CLIENT-HEADER:Some-Special-Header} ="yes"
+ run-plugin rate_limit.so "--limit=300 --error=429"
diff --git a/plugins/header_rewrite/CMakeLists.txt
b/plugins/header_rewrite/CMakeLists.txt
index b60db87226..fbc919f58d 100644
--- a/plugins/header_rewrite/CMakeLists.txt
+++ b/plugins/header_rewrite/CMakeLists.txt
@@ -38,7 +38,7 @@ target_link_libraries(header_rewrite_parser PUBLIC
libswoc::libswoc)
target_link_libraries(
header_rewrite
- PRIVATE PCRE::PCRE
+ PRIVATE ts::tscore PCRE::PCRE
PUBLIC libswoc::libswoc
)
@@ -52,7 +52,7 @@ if(BUILD_TESTING)
add_executable(test_header_rewrite header_rewrite_test.cc)
add_test(NAME test_header_rewrite COMMAND $<TARGET_FILE:test_header_rewrite>)
- target_link_libraries(test_header_rewrite PRIVATE header_rewrite_parser)
+ target_link_libraries(test_header_rewrite PRIVATE header_rewrite_parser
ts::inkevent ts::tscore)
if(maxminddb_FOUND)
target_link_libraries(test_header_rewrite PRIVATE maxminddb::maxminddb)
diff --git a/plugins/header_rewrite/factory.cc
b/plugins/header_rewrite/factory.cc
index 0c2b55e2ec..531f390f24 100644
--- a/plugins/header_rewrite/factory.cc
+++ b/plugins/header_rewrite/factory.cc
@@ -75,6 +75,8 @@ operator_factory(const std::string &op)
o = new OperatorSetBody();
} else if (op == "set-http-cntl") {
o = new OperatorSetHttpCntl();
+ } else if (op == "run-plugin") {
+ o = new OperatorRunPlugin();
} else {
TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str());
diff --git a/plugins/header_rewrite/header_rewrite.cc
b/plugins/header_rewrite/header_rewrite.cc
index b2f095e12e..06ec0f1696 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -26,6 +26,8 @@
#include "ts/remap.h"
#include "ts/remap_version.h"
+#include "proxy/http/remap/PluginFactory.h"
+
#include "parser.h"
#include "ruleset.h"
#include "resources.h"
@@ -35,19 +37,27 @@
// Debugs
const char PLUGIN_NAME[] = "header_rewrite";
const char PLUGIN_NAME_DBG[] = "dbg_header_rewrite";
-
namespace header_rewrite_ns
{
DbgCtl dbg_ctl{PLUGIN_NAME_DBG};
DbgCtl pi_dbg_ctl{PLUGIN_NAME};
+
+PluginFactory plugin_factory;
} // namespace header_rewrite_ns
-static std::once_flag initGeoLibs;
+static std::once_flag initHRWLibs;
static void
-initGeoLib(const std::string &dbPath)
+initHRWLibraries(const std::string &dbPath)
{
+
header_rewrite_ns::plugin_factory.setRuntimeDir(RecConfigReadRuntimeDir()).addSearchDir(RecConfigReadPluginDir());
+
+ if (dbPath.empty()) {
+ return;
+ }
+
Dbg(pi_dbg_ctl, "Loading geo db %s", dbPath.c_str());
+
#if TS_USE_HRW_GEOIP
GeoIPConditionGeo::initLibrary(dbPath);
#elif TS_USE_HRW_MAXMINDDB
@@ -99,7 +109,7 @@ public:
return _rules[hook];
}
- bool parse_config(const std::string &fname, TSHttpHookID default_hook);
+ bool parse_config(const std::string &fname, TSHttpHookID default_hook, char
*from_url = nullptr, char *to_url = nullptr);
private:
bool add_rule(RuleSet *rule);
@@ -133,7 +143,7 @@ RulesConfig::add_rule(RuleSet *rule)
// anyways (or reload for remap.config), so not really in the critical path.
//
bool
-RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook)
+RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook,
char *from_url, char *to_url)
{
RuleSet *rule = nullptr;
std::string filename;
@@ -177,7 +187,7 @@ RulesConfig::parse_config(const std::string &fname,
TSHttpHookID default_hook)
continue;
}
- Parser p;
+ Parser p(from_url, to_url);
// Tokenize and parse this line
if (!p.parse_line(line)) {
@@ -356,7 +366,7 @@ TSPluginInit(int argc, const char *argv[])
Dbg(pi_dbg_ctl, "Global geo db %s", geoDBpath.c_str());
- std::call_once(initGeoLibs, [&geoDBpath]() { initGeoLib(geoDBpath); });
+ std::call_once(initHRWLibs, [&geoDBpath]() { initHRWLibraries(geoDBpath); });
// Parse the global config file(s). All rules are just appended
// to the "global" Rules configuration.
@@ -414,6 +424,9 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char
* /* errbuf ATS_UNUSE
return TS_ERROR;
}
+ char *from_url = argv[0];
+ char *to_url = argv[1];
+
// argv contains the "to" and "from" URLs. Skip the first so that the
// second one poses as the program name.
--argc;
@@ -439,15 +452,15 @@ TSRemapNewInstance(int argc, char *argv[], void **ih,
char * /* errbuf ATS_UNUSE
}
Dbg(pi_dbg_ctl, "Remap geo db %s", geoDBpath.c_str());
-
- std::call_once(initGeoLibs, [&geoDBpath]() { initGeoLib(geoDBpath); });
}
+ std::call_once(initHRWLibs, [&geoDBpath]() { initHRWLibraries(geoDBpath); });
+
RulesConfig *conf = new RulesConfig;
for (int i = optind; i < argc; ++i) {
Dbg(pi_dbg_ctl, "Loading remap configuration file %s", argv[i]);
- if (!conf->parse_config(argv[i], TS_REMAP_PSEUDO_HOOK)) {
+ if (!conf->parse_config(argv[i], TS_REMAP_PSEUDO_HOOK, from_url, to_url)) {
TSError("[%s] Unable to create remap instance", PLUGIN_NAME);
delete conf;
return TS_ERROR;
@@ -473,6 +486,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char
* /* errbuf ATS_UNUSE
void
TSRemapDeleteInstance(void *ih)
{
+ Dbg(pi_dbg_ctl, "Deleting RulesConfig");
delete static_cast<RulesConfig *>(ih);
}
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index 2d23a9ac94..f12339346e 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -27,6 +27,8 @@
#include "tscore/ink_defs.h"
#include "tscore/ink_platform.h"
+#include "proxy/http/remap/PluginFactory.h"
+
#define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last
hook" for remap instances.
std::string getIP(sockaddr const *s_sockaddr);
@@ -38,7 +40,8 @@ extern const char PLUGIN_NAME_DBG[];
namespace header_rewrite_ns
{
-extern DbgCtl dbg_ctl;
-extern DbgCtl pi_dbg_ctl;
+extern DbgCtl dbg_ctl;
+extern DbgCtl pi_dbg_ctl;
+extern PluginFactory plugin_factory;
} // namespace header_rewrite_ns
using namespace header_rewrite_ns;
diff --git a/plugins/header_rewrite/operator.h
b/plugins/header_rewrite/operator.h
index 84855e9a38..27e800fa62 100644
--- a/plugins/header_rewrite/operator.h
+++ b/plugins/header_rewrite/operator.h
@@ -45,6 +45,8 @@ class Operator : public Statement
public:
Operator() { Dbg(dbg_ctl, "Calling CTOR for Operator"); }
+ virtual ~Operator() = default; // Very uncommon for an Operator to have a
custom DTOR, but happens.
+
// noncopyable
Operator(const Operator &) = delete;
void operator=(const Operator &) = delete;
diff --git a/plugins/header_rewrite/operators.cc
b/plugins/header_rewrite/operators.cc
index f4b652f939..f6f96b15f1 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -22,8 +22,10 @@
#include <arpa/inet.h>
#include <cstring>
#include <algorithm>
+#include <iomanip>
#include "ts/ts.h"
+#include "swoc/swoc_file.h"
#include "operators.h"
#include "ts/apidefs.h"
@@ -1092,6 +1094,7 @@ OperatorSetHttpCntl::initialize_hooks()
static const char *const HttpCntls[] = {
"LOGGING", "INTERCEPT_RETRY", "RESP_CACHEABLE", "REQ_CACHEABLE",
"SERVER_NO_STORE", "TXN_DEBUG", "SKIP_REMAP",
};
+
void
OperatorSetHttpCntl::exec(const Resources &res) const
{
@@ -1103,3 +1106,73 @@ OperatorSetHttpCntl::exec(const Resources &res) const
Dbg(pi_dbg_ctl, " Turning OFF %s for transaction",
HttpCntls[static_cast<size_t>(_cntl_qual)]);
}
}
+
+void
+OperatorRunPlugin::initialize(Parser &p)
+{
+ Operator::initialize(p);
+
+ auto plugin_name = p.get_arg();
+ auto plugin_args = p.get_value();
+
+ if (plugin_name.empty()) {
+ TSError("[%s] missing plugin name", PLUGIN_NAME);
+ return;
+ }
+
+ std::vector<std::string> tokens;
+ std::istringstream iss(plugin_args);
+ std::string token;
+
+ while (iss >> std::quoted(token)) {
+ tokens.push_back(token);
+ }
+
+ // Create argc and argv
+ int argc = tokens.size() + 2;
+ char **argv = new char *[argc];
+
+ argv[0] = p.from_url();
+ argv[1] = p.to_url();
+
+ for (int i = 0; i < argc; ++i) {
+ argv[i + 2] = const_cast<char *>(tokens[i].c_str());
+ }
+
+ std::string error;
+
+ // We have to escalate access while loading these plugins, just as done when
loading remap.config
+ {
+ uint32_t elevate_access = 0;
+
+ REC_ReadConfigInteger(elevate_access, "proxy.config.plugin.load_elevated");
+ ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
+
+ _plugin = plugin_factory.getRemapPlugin(swoc::file::path(plugin_name),
argc, const_cast<char **>(argv), error,
+ isPluginDynamicReloadEnabled());
+ } // done elevating access
+
+ delete[] argv;
+
+ if (!_plugin) {
+ TSError("[%s] Unable to load plugin '%s': %s", PLUGIN_NAME,
plugin_name.c_str(), error.c_str());
+ }
+}
+
+void
+OperatorRunPlugin::initialize_hooks()
+{
+ add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+
+ require_resources(RSRC_CLIENT_REQUEST_HEADERS); // Need this for the txnp
+}
+
+void
+OperatorRunPlugin::exec(const Resources &res) const
+{
+ TSReleaseAssert(_plugin != nullptr);
+
+ if (res._rri && res.txnp) {
+ _plugin->doRemap(res.txnp, res._rri);
+ }
+}
diff --git a/plugins/header_rewrite/operators.h
b/plugins/header_rewrite/operators.h
index 8fea20bef2..3f30d2654d 100644
--- a/plugins/header_rewrite/operators.h
+++ b/plugins/header_rewrite/operators.h
@@ -443,3 +443,35 @@ private:
bool _flag = false;
TSHttpCntlType _cntl_qual;
};
+
+class RemapPluginInst; // Opaque to the HRW operator, but needed in the
implementation.
+
+class OperatorRunPlugin : public Operator
+{
+public:
+ OperatorRunPlugin() { Dbg(dbg_ctl, "Calling CTOR for OperatorRunPlugin"); }
+
+ // This one is special, since we have to remove the old plugin from the
factory.
+ ~OperatorRunPlugin() override
+ {
+ Dbg(dbg_ctl, "Calling DTOR for OperatorRunPlugin");
+
+ if (_plugin) {
+ _plugin->done();
+ _plugin = nullptr;
+ }
+ }
+
+ // noncopyable
+ OperatorRunPlugin(const OperatorRunPlugin &) = delete;
+ void operator=(const OperatorRunPlugin &) = delete;
+
+ void initialize(Parser &p) override;
+
+protected:
+ void initialize_hooks() override;
+ void exec(const Resources &res) const override;
+
+private:
+ RemapPluginInst *_plugin = nullptr;
+};
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index aef6cb4b4d..25bfdd0a70 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -33,12 +33,26 @@
class Parser
{
public:
- Parser(){};
+ Parser() = default; // No from/to URLs for this parser
+ Parser(char *from_url, char *to_url) : _from_url(from_url), _to_url(to_url)
{}
// noncopyable
Parser(const Parser &) = delete;
void operator=(const Parser &) = delete;
+ // These are not const char *, because, you know, everything else with argv
is a char *
+ char *
+ from_url() const
+ {
+ return _from_url;
+ }
+
+ char *
+ to_url() const
+ {
+ return _to_url;
+ }
+
bool
empty() const
{
@@ -95,8 +109,10 @@ public:
private:
bool preprocess(std::vector<std::string> tokens);
- bool _cond = false;
- bool _empty = false;
+ bool _cond = false;
+ bool _empty = false;
+ char *_from_url = nullptr;
+ char *_to_url = nullptr;
std::vector<std::string> _mods;
std::string _op;
std::string _arg;