This is an automated email from the ASF dual-hosted git repository.
amc 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 cbe3455 TLS Bridge: Add "--file" option to configuration.
cbe3455 is described below
commit cbe34550940d0d68061bcdbae2124ab44ae6c43d
Author: Alan M. Carroll <[email protected]>
AuthorDate: Mon Dec 3 12:32:43 2018 -0600
TLS Bridge: Add "--file" option to configuration.
---
.../plugins/example-plugins/tls_bridge.en.rst | 38 ++++++++-
plugins/experimental/tls_bridge/tls_bridge.cc | 90 +++++++++++++++++++---
2 files changed, 118 insertions(+), 10 deletions(-)
diff --git a/doc/developer-guide/plugins/example-plugins/tls_bridge.en.rst
b/doc/developer-guide/plugins/example-plugins/tls_bridge.en.rst
index 85836c2..57a4661 100644
--- a/doc/developer-guide/plugins/example-plugins/tls_bridge.en.rst
+++ b/doc/developer-guide/plugins/example-plugins/tls_bridge.en.rst
@@ -13,7 +13,7 @@
.. include:: ../../../common.defs
-.. highlight:: cpp
+.. highlight:: text
.. default-domain:: cpp
.. |Name| replace:: TLS Bridge
@@ -150,6 +150,42 @@ ingress |TS|, and the peer |TS| connects to the service.
tls_bridge.so .*[.]service[.]com peer.ats:4443 .*[.]altsvc.ats
altpeer.ats:4443
+ Mappings can also be specified in an external file. For instance, if the
file named "bridge.config" in the default |TS|
+ configuration directory contained mappings, the ``plugin.config``
configuration line could look
+ like ::
+
+ tls_bridge.so .*[.]service[.]com peer.ats:4443 --file bridge.config
+
+ or
+
+ tls_bridge.so --file bridge.config .*[.]service[.]com peer.ats:4443
+
+ These are not identical - direct mappings and file mappings are processed
in order. This means in
+ the first example, the direct mapping is checked before any mappping in
"bridge.config", and in
+ the latter example the mappings in "bridge.config" are checked before the
direct mapping. There
+ can be multiple "--file" arguments, which are processed in the order they
appear in
+ "plugin.config". The file name can be absolute, or relative. If the file
name is relative, it is
+ relative to the |TS| configuration directory. Therefore, in these examples,
"bridge.config" must
+ be in the same directory as ``plugin.config``.
+
+ The contents of "bridge.config" must be one mapping per line, with a
regular expression separated
+ by white space from the destination service. This is identical to the
format in ``plugin.config``
+ except there is only one pair per line. E.g., valid content for
"bridge.config" could be ::
+
+ # Primary service location.
+ .*[.]service[.]com peer.ats:4443
+
+ # Secondary.
+ .*[.]altsvc.ats altpeer.ats:4443
+
+ Leading whitespace on a line is ignored, and if the first non-whitespace
character is '#' then
+ the entire line is ignored. Therefore if that is the content of
"bridge.config", these two
+ lines in "plugin.config" would behave identically ::
+
+ tls_bridge.so --file bridge.config
+
+ tls_bridge.so .*[.]service[.]com peer.ats:4443 .*[.]altsvc.ats
altpeer.ats:4443
+
Notes
=====
diff --git a/plugins/experimental/tls_bridge/tls_bridge.cc
b/plugins/experimental/tls_bridge/tls_bridge.cc
index 7cfe1e5..368a808 100644
--- a/plugins/experimental/tls_bridge/tls_bridge.cc
+++ b/plugins/experimental/tls_bridge/tls_bridge.cc
@@ -21,6 +21,7 @@
#include <vector>
#include <cinttypes>
#include "tscpp/util/TextView.h"
+#include "tscore/ts_file.h"
#include "regex.h"
#define PLUGIN_NAME "TLS Bridge"
@@ -28,10 +29,15 @@
using ts::TextView;
+namespace
+{
// Base format string for making the internal CONNECT.
char const CONNECT_FORMAT[] = "CONNECT https:%.*s HTTP/1.1\r\n\r\n";
const TextView METHOD_CONNECT{TS_HTTP_METHOD_CONNECT, TS_HTTP_LEN_CONNECT};
+constexpr TextView CONFIG_FILE_ARG{"--file"};
+const std::string TS_CONFIG_DIR{TSConfigDirGet()};
+}; // namespace
/*
------------------------------------------------------------------------------------
*/
// Utility functions
@@ -64,11 +70,11 @@ class BridgeConfig
using self_type = BridgeConfig;
/// Construct an item.
- Item(const char *pattern, Regex &&r, const char *dest) :
_pattern(pattern), _r(std::move(r)), _dest(dest) {}
+ Item(std::string_view pattern, Regex &&r, std::string_view service) :
_pattern(pattern), _r(std::move(r)), _service(service) {}
std::string _pattern; ///< Original configuration regular expression.
Regex _r; ///< Compiled regex.
- std::string _dest; ///< Destination if matched.
+ std::string _service; ///< Destination service if matched.
};
public:
@@ -83,6 +89,14 @@ public:
private:
/// Configuration item storage.
std::vector<Item> _items;
+
+ /** Load a configuration item pair.
+ *
+ * @param rxp The regular expression to match.
+ * @param service The destination service.
+ * @param ln Line number, or 0 if from plugin.config.
+ */
+ void load_pair(std::string_view rxp, std::string_view service,
ts::file::path const &src, int ln = 0);
};
inline int
@@ -92,17 +106,75 @@ BridgeConfig::count() const
}
void
+BridgeConfig::load_pair(std::string_view rxp, std::string_view service,
ts::file::path const &src, int ln)
+{
+ Regex r;
+ if (r.compile(rxp.data(), Regex::ANCHORED)) {
+ _items.emplace_back(rxp, std::move(r), service);
+ } else {
+ char buff[std::numeric_limits<int>::digits10 + 2];
+ char const *place = "";
+ if (ln) {
+ snprintf(buff, sizeof(buff), " on line %d", ln);
+ place = buff;
+ }
+ TSError("%s: Failed to compile regular expression '%.*s' in %s%s",
PLUGIN_TAG, int(rxp.size()), rxp.data(), src.c_str(), place);
+ }
+}
+
+void
BridgeConfig::load_config(int argc, const char *argv[])
{
+ static const ts::file::path plugin_config_fp{"plugin.config"};
+
for (int i = 0; i < argc; i += 2) {
- Regex r;
- if (i + 1 >= argc) {
- TSError("%s: Destination regular expression without peer", PLUGIN_TAG);
+ if (argv[i] == CONFIG_FILE_ARG) {
+ if (i + 1 >= argc) {
+ TSError("%s: Invalid '%.*s' argument - no file name found.",
PLUGIN_TAG, int(CONFIG_FILE_ARG.size()),
+ CONFIG_FILE_ARG.data());
+ } else {
+ ts::file::path fp(argv[i + 1]);
+ std::error_code ec;
+ if (!fp.is_absolute()) {
+ fp = ts::file::path{TS_CONFIG_DIR} / fp; // slap the config dir on
it to make it absolute.
+ }
+ // bulk load the file.
+ std::string content{ts::file::load(fp, ec)};
+ if (ec) {
+ TSError("%s: Invalid '%.*s' argument - unable to read file '%s' :
%s.", PLUGIN_TAG, int(CONFIG_FILE_ARG.size()),
+ CONFIG_FILE_ARG.data(), fp.c_str(), ec.message().c_str());
+
+ } else {
+ // walk the lines.
+ int line_no = 0;
+ TextView src{content};
+ while (!src.empty()) {
+ TextView line{src.take_prefix_at('\n').trim_if(&isspace)};
+ ++line_no;
+ if (line.empty() || '#' == *line)
+ continue; // empty or comment, ignore.
+
+ // Pick apart the line into the regular expression and destination
service.
+ TextView service{line};
+ TextView rxp{service.take_prefix_if(&isspace)};
+ service.ltrim_if(&isspace); // dump extra separating space.
+ // Only need to check service, as if the line isn't empty rxp will
also be non-empty.
+ if (service.empty()) {
+ TSError("%s: Invalid line %d in '%s' - no destination service
found.", PLUGIN_TAG, line_no, fp.c_str());
+ } else {
+ this->load_pair(rxp, service, fp, line_no);
+ }
+ }
+ }
+ }
+ } else if (argv[i][0] == '-') {
+ TSError("%s: Unrecognized option '%s'", PLUGIN_TAG, argv[i]);
+ i -= 1; // Don't skip next arg.
} else {
- if (r.compile(argv[i]), Regex::ANCHORED) {
- _items.emplace_back(argv[i], std::move(r), argv[i + 1]);
+ if (i + 1 >= argc) {
+ TSError("%s: Regular expression '%s' without destination service",
PLUGIN_TAG, argv[i]);
} else {
- TSError("%s: Failed to compile regular expression '%s'", PLUGIN_TAG,
argv[i]);
+ this->load_pair(argv[i], argv[i + 1], plugin_config_fp);
}
}
}
@@ -113,7 +185,7 @@ BridgeConfig::match(TextView name)
{
for (auto &item : _items) {
if (item._r.exec(name)) {
- return {item._dest};
+ return {item._service};
}
}
return {};