This is an automated email from the ASF dual-hosted git repository. masaori 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 27478a7ffe Add config file support for cache_fill (#12347) 27478a7ffe is described below commit 27478a7ffea60dfc1d7ad27bf1cb4fd2413f0943 Author: Jasmine Emanouel <40879549+jasmine-nahr...@users.noreply.github.com> AuthorDate: Thu Jul 17 11:14:28 2025 +1000 Add config file support for cache_fill (#12347) --- plugins/experimental/cache_fill/CMakeLists.txt | 2 +- plugins/experimental/cache_fill/cache_fill.cc | 80 +++++++--- plugins/experimental/cache_fill/configs.cc | 213 +++++++++++++++++++++++++ plugins/experimental/cache_fill/configs.h | 89 +++++++++++ plugins/experimental/cache_fill/rules.cc | 145 +++++++++++++++++ plugins/experimental/cache_fill/rules.h | 78 +++++++++ 6 files changed, 583 insertions(+), 24 deletions(-) diff --git a/plugins/experimental/cache_fill/CMakeLists.txt b/plugins/experimental/cache_fill/CMakeLists.txt index 5a780a7f60..61f1da8764 100644 --- a/plugins/experimental/cache_fill/CMakeLists.txt +++ b/plugins/experimental/cache_fill/CMakeLists.txt @@ -15,6 +15,6 @@ # ####################### -add_atsplugin(cache_fill background_fetch.cc cache_fill.cc) +add_atsplugin(cache_fill background_fetch.cc cache_fill.cc configs.cc rules.cc) verify_remap_plugin(cache_fill) diff --git a/plugins/experimental/cache_fill/cache_fill.cc b/plugins/experimental/cache_fill/cache_fill.cc index c1df5d1451..ad3d90c3bf 100644 --- a/plugins/experimental/cache_fill/cache_fill.cc +++ b/plugins/experimental/cache_fill/cache_fill.cc @@ -39,6 +39,7 @@ #include "ts/remap.h" #include "ts/remap_version.h" #include "background_fetch.h" +#include "configs.h" static const char * getCacheLookupResultName(TSCacheLookupResult result) @@ -108,23 +109,29 @@ cont_check_cacheable(TSHttpTxn txnp) // if a background fetch is allowed for this request // static int -cont_handle_cache(TSCont /* contp ATS_UNUSED */, TSEvent event, void *edata) +cont_handle_cache(TSCont contp, TSEvent event, void *edata) { - TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); - if (TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE == event) { - bool const requested = cont_check_cacheable(txnp); - if (requested) // Made a background fetch request, do not cache the response - { - Dbg(dbg_ctl, "setting no store"); - TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true); - TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_MISS); - } + TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); + BgFetchConfig *config = static_cast<BgFetchConfig *>(TSContDataGet(contp)); + + if (nullptr == config) { + // something seriously wrong.. + TSError("[%s] Can't get configurations", PLUGIN_NAME); + } else if (config->bgFetchAllowed(txnp)) { + if (TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE == event) { + bool const requested = cont_check_cacheable(txnp); + if (requested) // Made a background fetch request, do not cache the response + { + Dbg(dbg_ctl, "setting no store"); + TSHttpTxnCntlSet(txnp, TS_HTTP_CNTL_SERVER_NO_STORE, true); + TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_MISS); + } - } else { - TSError("[%s] Unknown event for this plugin %d", PLUGIN_NAME, event); - Dbg(dbg_ctl, "unknown event for this plugin %d", event); + } else { + TSError("[%s] Unknown event for this plugin %d", PLUGIN_NAME, event); + Dbg(dbg_ctl, "unknown event for this plugin %d", event); + } } - // Reenable and continue with the state machine. TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return 0; @@ -147,19 +154,46 @@ TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) // We don't have any specific "instances" here, at least not yet. // TSReturnCode -TSRemapNewInstance(int /* argc ATS_UNUSED */, char ** /* argv ATS_UNUSED */, void **ih, char * /* errbuf ATS_UNUSED */, - int /* errbuf_size ATS_UNUSED */) +TSRemapNewInstance(int argc, char **argv, void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */) { - TSCont cont = TSContCreate(cont_handle_cache, nullptr); - *ih = cont; - return TS_SUCCESS; + TSCont cont = TSContCreate(cont_handle_cache, nullptr); + BgFetchConfig *config = new BgFetchConfig(cont); + bool success = true; + + // The first two arguments are the "from" and "to" URL string. We need to + // skip them, but we also require that there be an option to masquerade as + // argv[0], so we increment the argument indexes by 1 rather than by 2. + argc--; + argv++; + + // This is for backwards compatibility, ugly! ToDo: Remove for ATS v9.0.0 IMO. + if (argc > 1 && *argv[1] != '-') { + Dbg(dbg_ctl, "config file %s", argv[1]); + if (!config->readConfig(argv[1])) { + success = false; + } + } else { + if (!config->parseOptions(argc, const_cast<const char **>(argv))) { + success = false; + } + } + + if (success) { + *ih = config; + + return TS_SUCCESS; + } + + // Something went wrong with the configuration setup. + delete config; + return TS_ERROR; } void TSRemapDeleteInstance(void *ih) { - TSCont cont = static_cast<TSCont>(ih); - TSContDestroy(cont); + BgFetchConfig *config = static_cast<BgFetchConfig *>(ih); + delete config; } /////////////////////////////////////////////////////////////////////////////// @@ -171,8 +205,8 @@ TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */) if (nullptr == ih) { return TSREMAP_NO_REMAP; } - TSCont const cont = static_cast<TSCont>(ih); - TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, cont); + BgFetchConfig *config = static_cast<BgFetchConfig *>(ih); + TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, config->getCont()); Dbg(dbg_ctl, "TSRemapDoRemap() added hook"); return TSREMAP_NO_REMAP; diff --git a/plugins/experimental/cache_fill/configs.cc b/plugins/experimental/cache_fill/configs.cc new file mode 100644 index 0000000000..9eef083b8d --- /dev/null +++ b/plugins/experimental/cache_fill/configs.cc @@ -0,0 +1,213 @@ +/** @file + + Plugin to perform background fetches of certain content that would + otherwise not be cached. For example, Range: requests / responses. + + @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 <getopt.h> +#include <cstdio> +#include <memory.h> + +#include "configs.h" +#include "background_fetch.h" + +#include <swoc/TextView.h> +#include <swoc/swoc_file.h> +#include <tsutil/ts_bw_format.h> + +using namespace swoc::literals; + +// Parse the command line options. This got a little wonky, since we decided to have different +// syntax for remap vs global plugin initialization, and the global BG fetch state :-/. Clean up +// later... +bool +BgFetchConfig::parseOptions(int argc, const char *argv[]) +{ + static const struct option longopt[] = { + {const_cast<char *>("log"), required_argument, nullptr, 'l' }, + {const_cast<char *>("config"), required_argument, nullptr, 'c' }, + {const_cast<char *>("allow-304"), no_argument, nullptr, 'a' }, + {nullptr, no_argument, nullptr, '\0'} + }; + + while (true) { + int opt = getopt_long(argc, const_cast<char *const *>(argv), "lc", longopt, nullptr); + + if (opt == -1) { + break; + } + + switch (opt) { + case 'l': + Dbg(dbg_ctl, "option: log file specified: %s", optarg); + _log_file = optarg; + break; + case 'c': + Dbg(dbg_ctl, "option: config file '%s'", optarg); + if (!readConfig(optarg)) { + // Error messages are written in the parser + return false; + } + break; + case 'a': + Dbg(dbg_ctl, "option: --allow-304 set"); + _allow_304 = true; + break; + default: + TSError("[%s] invalid plugin option: %c", PLUGIN_NAME, opt); + return false; + break; + } + } + + return true; +} + +// Read a config file, populate the linked list (chain the BgFetchRule's) +bool +BgFetchConfig::readConfig(const char *config_file) +{ + if (nullptr == config_file) { + TSError("[%s] invalid config file", PLUGIN_NAME); + return false; + } + + swoc::file::path path(config_file); + + Dbg(dbg_ctl, "trying to open config file in this path: %s", config_file); + + if (!path.is_absolute()) { + path = swoc::file::path(TSConfigDirGet()) / path; + } + Dbg(dbg_ctl, "chosen config file is at: %s", path.c_str()); + + std::error_code ec; + auto content = swoc::file::load(path, ec); + if (ec) { + swoc::bwprint(ts::bw_dbg, "[{}] invalid config file: {} {}", PLUGIN_NAME, path, ec); + TSError("%s", ts::bw_dbg.c_str()); + Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str()); + return false; + } + + swoc::TextView text{content}; + while (text) { + auto line = text.take_prefix_at('\n').ltrim_if(&isspace); + + if (line.empty() || line.front() == '#') { + continue; + } + + auto cfg_type = line.take_prefix_if(&isspace); + if (cfg_type.empty()) { + continue; + } + + Dbg(dbg_ctl, "setting background_fetch exclusion criterion based on string: %.*s", int(cfg_type.size()), cfg_type.data()); + + bool exclude = false; + if (0 == strcasecmp(cfg_type, "exclude")) { + exclude = true; + } else if (0 != strcasecmp(cfg_type, "include")) { + swoc::bwprint(ts::bw_dbg, "[{}] invalid specifier {}, skipping config line", PLUGIN_NAME, cfg_type); + TSError("%s", ts::bw_dbg.c_str()); + continue; + } + + if (auto cfg_name = line.take_prefix_if(&isspace); !cfg_name.empty()) { + if (auto cfg_value = line.take_prefix_if(&isspace); !cfg_value.empty()) { + if ("Client-IP"_tv == cfg_name) { + swoc::IPRange r; + // '*' is special - match any address. Signalled by empty range. + if (cfg_value.size() != 1 || cfg_value.front() == '*') { + if (!r.load(cfg_value)) { // assume if it loads, it's not empty. + TSError("[%s] invalid IP address range %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()), + cfg_value.data()); + continue; + } + } + _rules.emplace_back(exclude, r); + swoc::bwprint(ts::bw_dbg, "adding background_fetch address range rule {} for {}: {}", exclude, cfg_name, cfg_value); + Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str()); + } else if ("Content-Length"_tv == cfg_name) { + BgFetchRule::size_cmp_type::OP op; + if (cfg_value[0] == '<') { + op = BgFetchRule::size_cmp_type::LESS_THAN_OR_EQUAL; + } else if (cfg_value[0] == '>') { + op = BgFetchRule::size_cmp_type::LESS_THAN_OR_EQUAL; + } else { + TSError("[%s] invalid Content-Length condition %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()), + cfg_value.data()); + continue; + } + ++cfg_value; // Drop leading character. + swoc::TextView parsed; + auto n = swoc::svtou(cfg_value, &parsed); + if (parsed.size() != cfg_value.size()) { + TSError("[%s] invalid Content-Length size value %.*s, skipping config value", PLUGIN_NAME, int(cfg_value.size()), + cfg_value.data()); + continue; + } + _rules.emplace_back(exclude, op, size_t(n)); + + swoc::bwprint(ts::bw_dbg, "adding background_fetch content length rule {} for {}: {}", exclude, cfg_name, cfg_value); + Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str()); + } else { + _rules.emplace_back(exclude, cfg_name, cfg_value); + swoc::bwprint(ts::bw_dbg, "adding background_fetch field compare rule {} for {}: {}", exclude, cfg_name, cfg_value); + Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str()); + } + } else { + TSError("[%s] invalid value %.*s, skipping config line", PLUGIN_NAME, int(cfg_name.size()), cfg_name.data()); + } + } + } + + Dbg(dbg_ctl, "Done parsing config"); + + return true; +} + +/////////////////////////////////////////////////////////////////////////// +// Check the configuration (either per remap, or global), and decide if +// this request is allowed to trigger a background fetch. +// +bool +BgFetchConfig::bgFetchAllowed(TSHttpTxn txnp) const +{ + Dbg(dbg_ctl, "Testing: request is internal?"); + if (TSHttpTxnIsInternal(txnp)) { + return false; + } + + bool allow_bg_fetch = true; + + // We could do this recursively, but following the linked list is probably more efficient. + for (auto const &r : _rules) { + if (r.check_field_configured(txnp)) { + Dbg(dbg_ctl, "found %s rule match", r._exclude ? "exclude" : "include"); + allow_bg_fetch = !r._exclude; + break; + } + } + + return allow_bg_fetch; +} diff --git a/plugins/experimental/cache_fill/configs.h b/plugins/experimental/cache_fill/configs.h new file mode 100644 index 0000000000..7d3304cea8 --- /dev/null +++ b/plugins/experimental/cache_fill/configs.h @@ -0,0 +1,89 @@ +/** @file + + Plugin to perform background fetches of certain content that would + otherwise not be cached. For example, Range: requests / responses. + + @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. +*/ + +#pragma once + +#include <cstdlib> +#include <atomic> +#include <string> +#include <list> +#include <memory> +#include <sys/socket.h> + +#include "rules.h" + +/////////////////////////////////////////////////////////////////////////// +// This holds one complete background fetch rule +// +class BgFetchConfig +{ +public: + using list_type = std::list<BgFetchRule>; + + explicit BgFetchConfig(TSCont cont) : _cont(cont) { TSContDataSet(cont, static_cast<void *>(this)); } + + ~BgFetchConfig() + { + if (_cont) { + TSContDestroy(_cont); + } + } + + bool parseOptions(int argc, const char *argv[]); + + list_type const & + getRules() const + { + return _rules; + } + + TSCont + getCont() const + { + return _cont; + } + + const std::string & + logFile() const + { + return _log_file; + } + + bool + allow304() const + { + return _allow_304; + } + + // This parses and populates the BgFetchRule linked list (_rules). + bool readConfig(const char *file_name); + + bool bgFetchAllowed(TSHttpTxn txnp) const; + +private: + TSCont _cont = nullptr; + list_type _rules; + bool _allow_304 = false; + std::string _log_file; +}; diff --git a/plugins/experimental/cache_fill/rules.cc b/plugins/experimental/cache_fill/rules.cc new file mode 100644 index 0000000000..9fc5fea31b --- /dev/null +++ b/plugins/experimental/cache_fill/rules.cc @@ -0,0 +1,145 @@ +/** @file + + Plugin to perform background fetches of certain content that would + otherwise not be cached. For example, Range: requests / responses. + + @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 <cstdlib> +#include <string_view> +#include <cstring> +#include <sys/socket.h> + +#include <swoc/IPEndpoint.h> +#include <swoc/swoc_meta.h> + +#include "configs.h" +#include "rules.h" +#include "background_fetch.h" + +#include "tsutil/ts_bw_format.h" +#include "tsutil/ts_ip.h" + +/////////////////////////////////////////////////////////////////////////// +// These are little helper functions for the main rules evaluator. +// +static bool +check_value(TSHttpTxn txnp, swoc::IPRange const &range) +{ + const sockaddr *client_ip = TSHttpTxnClientAddrGet(txnp); + if (!client_ip) { + return false; + } + + if (range.empty()) { // this means "match any address". + return true; + } + + swoc::IPEndpoint client_addr{client_ip}; + + swoc::bwprint(ts::bw_dbg, "cfg_ip {::c}, client_ip {}", range, client_addr); + Dbg(dbg_ctl, "%s", ts::bw_dbg.c_str()); + + if (client_addr.family() == range.family()) { + return (range.is_ip4() && range.ip4().contains(swoc::IP4Addr(client_addr.ip4()))) || + (range.is_ip6() && range.ip6().contains(swoc::IP6Addr(client_addr.ip6()))); + } + + return false; // Different family, no match. +} + +static bool +check_value(TSHttpTxn txnp, BgFetchRule::size_cmp_type const &cmp) +{ + TSMBuffer hdr_bufp; + TSMLoc hdr_loc; + + if (TS_SUCCESS != TSHttpTxnServerRespGet(txnp, &hdr_bufp, &hdr_loc)) { + TSError("[%s] Failed to get resp headers", PLUGIN_NAME); + return false; + } + + TSMLoc loc = TSMimeHdrFieldFind(hdr_bufp, hdr_loc, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH); + if (TS_NULL_MLOC == loc) { + Dbg(dbg_ctl, "No content-length field in resp"); + return false; // Field not found. + } + + auto content_len = TSMimeHdrFieldValueUintGet(hdr_bufp, hdr_loc, loc, 0 /* index */); + TSHandleMLocRelease(hdr_bufp, hdr_loc, loc); + + if (cmp._op == BgFetchRule::size_cmp_type::OP::GREATER_THAN_OR_EQUAL) { + return content_len >= cmp._size; + } else if (cmp._op == BgFetchRule::size_cmp_type::OP::LESS_THAN_OR_EQUAL) { + return content_len <= cmp._size; + } + + return false; +} + +static bool +check_value(TSHttpTxn txnp, BgFetchRule::field_cmp_type const &cmp) +{ + TSMBuffer hdr_bufp; + TSMLoc hdr_loc; + + if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &hdr_bufp, &hdr_loc)) { + TSError("[%s] Failed to get resp headers", PLUGIN_NAME); + return false; + } + + TSMLoc loc = TSMimeHdrFieldFind(hdr_bufp, hdr_loc, cmp._name.data(), cmp._name.size()); + + if (TS_NULL_MLOC == loc) { + Dbg(dbg_ctl, "no field %s in request header", cmp._name.c_str()); + return false; + } + + if (cmp._name.size() == 1 && cmp._name.front() == '*') { + Dbg(dbg_ctl, "Found %s wild card", cmp._name.c_str()); + return true; + } + + int val_len = 0; + char const *val_str = TSMimeHdrFieldValueStringGet(hdr_bufp, hdr_loc, loc, 0, &val_len); + bool zret = false; + + if (!val_str || val_len <= 0) { + Dbg(dbg_ctl, "invalid field"); + } else { + Dbg(dbg_ctl, "comparing with %s", cmp._value.c_str()); + zret = std::string_view::npos != std::string_view(val_str, val_len).find(cmp._value); + } + TSHandleMLocRelease(hdr_bufp, hdr_loc, loc); + return zret; +} + +/////////////////////////////////////////////////////////////////////////// +// Check if a header excludes us from running the background fetch +// +bool +BgFetchRule::check_field_configured(TSHttpTxn txnp) const +{ + return std::visit(swoc::meta::vary{[=](std::monostate) { return false; }, + [=](swoc::IPRange const &range) { return check_value(txnp, range); }, + [=](size_cmp_type const &cmp) { return check_value(txnp, cmp); }, + [=](field_cmp_type const &cmp) { return check_value(txnp, cmp); }}, + _value); +} diff --git a/plugins/experimental/cache_fill/rules.h b/plugins/experimental/cache_fill/rules.h new file mode 100644 index 0000000000..bbfc7a36ec --- /dev/null +++ b/plugins/experimental/cache_fill/rules.h @@ -0,0 +1,78 @@ +/** @file + + Plugin to perform background fetches of certain content that would + otherwise not be cached. For example, Range: requests / responses. + + @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. +*/ + +#pragma once + +#include <cstdlib> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <variant> +#include <sys/socket.h> + +#include <swoc/TextView.h> +#include <swoc/IPRange.h> + +#include "ts/ts.h" + +/////////////////////////////////////////////////////////////////////////// +// This is a linked list of rule entries. This gets stored and parsed with the +// BgFetchConfig object. +// +class BgFetchRule +{ + using self_type = BgFetchRule; + +public: + /// Content length / size comparison. + struct size_cmp_type { + enum OP { LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL } _op; ///< Comparison to use. + size_t _size; ///< Size for comparison. + }; + + /// Field value comparison. + struct field_cmp_type { + std::string _name; ///< Field name. + std::string _value; ///< Value to compare. A single '*' means match anything - check for field presence. + }; + + BgFetchRule(bool exc, size_cmp_type::OP op, size_t n) : _exclude(exc), _value(size_cmp_type{op, n}) {} + BgFetchRule(bool exc, swoc::IPRange const &range) : _exclude(exc), _value(range) {} + + BgFetchRule(bool exc, swoc::TextView name, swoc::TextView value) + : _exclude(exc), _value(field_cmp_type{std::string(name), std::string(value)}) + { + } + + BgFetchRule(self_type &&that) = default; + + // Main evaluation entry point. + bool bgFetchAllowed(TSHttpTxn txnp) const; + bool check_field_configured(TSHttpTxn txnp) const; + + bool _exclude; ///< Exclusion @c true or inclusion @c false. + + /// Value type for the rule, which also indicates the type of check. + using value_type = std::variant<std::monostate, size_cmp_type, field_cmp_type, swoc::IPRange>; + value_type _value; ///< Value instance for checking. +};