diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst b/doc/admin-guide/plugins/header_rewrite.en.rst index 554228a5ddf..4d331cf35a4 100644 --- a/doc/admin-guide/plugins/header_rewrite.en.rst +++ b/doc/admin-guide/plugins/header_rewrite.en.rst @@ -374,6 +374,14 @@ string. Therefore the condition is treated as if it were :: which is true when the connection is not TLS. The arguments ``H2``, ``IPV4``, and ``IPV6`` work the same way. +As a special matcher, the inbound IP addresses can be matched against a list of IP ranges, e.g. +:: + + cond %{INBOUND:REMOTE-ADDR} {192.168.201.0/24,10.0.0.0/8} + +Note that this will not work against the non-IP based conditions, such as the protocol families, +and the configuration parser will error out. The format here is very specific, in particular no +white spaces are allowed between the ranges. IP ~~ @@ -400,6 +408,13 @@ actually as a value to an operator, e.g. :: set-header X-Server-IP %{IP:SERVER} set-header X-Outbound-IP %{IP:OUTBOUND} +As a special matcher, the `IP` can be matched against a list of IP ranges, e.g. +:: + + cond %{IP:CLIENT} {192.168.201.0/24,10.0.0.0/8} + +The format here is very specific, in particular no white spaces are allowed between the +ranges. INTERNAL-TRANSACTION ~~~~~~~~~~~~~~~~~~~~ diff --git a/plugins/header_rewrite/Makefile.inc b/plugins/header_rewrite/Makefile.inc index a64fffa93d6..a74c2d6dc85 100644 --- a/plugins/header_rewrite/Makefile.inc +++ b/plugins/header_rewrite/Makefile.inc @@ -35,6 +35,8 @@ header_rewrite_header_rewrite_la_SOURCES = \ header_rewrite/operators.h \ header_rewrite/regex_helper.cc \ header_rewrite/regex_helper.h \ + header_rewrite/ipranges_helper.cc \ + header_rewrite/ipranges_helper.h \ header_rewrite/resources.cc \ header_rewrite/resources.h \ header_rewrite/ruleset.cc \ diff --git a/plugins/header_rewrite/condition.cc b/plugins/header_rewrite/condition.cc index f407778ed8b..717742d8c35 100644 --- a/plugins/header_rewrite/condition.cc +++ b/plugins/header_rewrite/condition.cc @@ -46,6 +46,10 @@ parse_matcher_op(std::string &arg) arg.erase(arg.length() - 1, arg.length()); return MATCH_REGULAR_EXPRESSION; break; + case '{': + arg.erase(0, 1); + arg.erase(arg.length() - 1, arg.length()); + return MATCH_IP_RANGES; default: return MATCH_EQUAL; break; diff --git a/plugins/header_rewrite/conditions.cc b/plugins/header_rewrite/conditions.cc index fa0e092ed92..3f7ebca116f 100644 --- a/plugins/header_rewrite/conditions.cc +++ b/plugins/header_rewrite/conditions.cc @@ -528,10 +528,17 @@ ConditionIp::initialize(Parser &p) { Condition::initialize(p); - MatcherType *match = new MatcherType(_cond_op); + if (_cond_op == MATCH_IP_RANGES) { // Special hack for IP ranges for now ... + MatcherTypeIp *match = new MatcherTypeIp(_cond_op); - match->set(p.get_arg()); - _matcher = match; + match->set(p.get_arg()); + _matcher = match; + } else { + MatcherType *match = new MatcherType(_cond_op); + + match->set(p.get_arg()); + _matcher = match; + } } void @@ -557,14 +564,39 @@ ConditionIp::set_qualifier(const std::string &q) bool ConditionIp::eval(const Resources &res) { - std::string s; + if (_matcher->op() == MATCH_IP_RANGES) { + const sockaddr *addr = nullptr; - append_value(s, res); - bool rval = static_cast *>(_matcher)->test(s); + switch (_ip_qual) { + case IP_QUAL_CLIENT: + addr = TSHttpTxnClientAddrGet(res.txnp); + break; + case IP_QUAL_INBOUND: + addr = TSHttpTxnIncomingAddrGet(res.txnp); + break; + case IP_QUAL_SERVER: + addr = TSHttpTxnServerAddrGet(res.txnp); + break; + case IP_QUAL_OUTBOUND: + addr = TSHttpTxnOutgoingAddrGet(res.txnp); + break; + } + + if (addr) { + return static_cast *>(_matcher)->test(addr); + } else { + return false; + } + } else { + std::string s; - TSDebug(PLUGIN_NAME, "Evaluating IP(): %s - rval: %d", s.c_str(), rval); + append_value(s, res); + bool rval = static_cast *>(_matcher)->test(s); - return rval; + TSDebug(PLUGIN_NAME, "Evaluating IP(): %s - rval: %d", s.c_str(), rval); + + return rval; + } } void @@ -1029,10 +1061,17 @@ ConditionInbound::initialize(Parser &p) { Condition::initialize(p); - MatcherType *match = new MatcherType(_cond_op); + if (_cond_op == MATCH_IP_RANGES) { // Special hack for IP ranges for now ... + MatcherTypeIp *match = new MatcherTypeIp(_cond_op); - match->set(p.get_arg()); - _matcher = match; + match->set(p.get_arg()); + _matcher = match; + } else { + MatcherType *match = new MatcherType(_cond_op); + + match->set(p.get_arg()); + _matcher = match; + } } void @@ -1070,14 +1109,38 @@ ConditionInbound::set_qualifier(const std::string &q) bool ConditionInbound::eval(const Resources &res) { - std::string s; + // Special hack for IP-Ranges since we really don't need to do a string conversion for the comparison. + if (_matcher->op() == MATCH_IP_RANGES) { + const sockaddr *addr = nullptr; - append_value(s, res); - bool rval = static_cast *>(_matcher)->test(s); + switch (_net_qual) { + case NET_QUAL_LOCAL_ADDR: + addr = TSHttpTxnIncomingAddrGet(res.txnp); + break; + case NET_QUAL_REMOTE_ADDR: + addr = TSHttpTxnClientAddrGet(res.txnp); + break; + default: + // Only support actual IP addresses of course... + TSError("[%s] %%{%s:%s} is not supported, only IP-Addresses allowed", PLUGIN_NAME, TAG, get_qualifier().c_str()); + break; + } + + if (addr) { + return static_cast *>(_matcher)->test(addr); + } else { + return false; + } + } else { + std::string s; - TSDebug(PLUGIN_NAME, "Evaluating %s(): %s - rval: %d", TAG, s.c_str(), rval); + append_value(s, res); + bool rval = static_cast *>(_matcher)->test(s); - return rval; + TSDebug(PLUGIN_NAME, "Evaluating %s(): %s - rval: %d", TAG, s.c_str(), rval); + + return rval; + } } void diff --git a/plugins/header_rewrite/conditions.h b/plugins/header_rewrite/conditions.h index 323a5bacee1..aedac2168d3 100644 --- a/plugins/header_rewrite/conditions.h +++ b/plugins/header_rewrite/conditions.h @@ -348,6 +348,7 @@ class ConditionInternalTxn : public Condition class ConditionIp : public Condition { typedef Matchers MatcherType; + typedef Matchers MatcherTypeIp; public: explicit ConditionIp() { TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for ConditionIp"); }; @@ -503,8 +504,9 @@ class ConditionCidr : public Condition /// Information about the inbound (client) session. class ConditionInbound : public Condition { - using MatcherType = Matchers; - using self = ConditionInbound; + using MatcherType = Matchers; + using MatcherTypeIp = Matchers; + using self = ConditionInbound; public: explicit ConditionInbound() { TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for ConditionInbound"); }; diff --git a/plugins/header_rewrite/ipranges_helper.cc b/plugins/header_rewrite/ipranges_helper.cc new file mode 100644 index 00000000000..35ee9ded449 --- /dev/null +++ b/plugins/header_rewrite/ipranges_helper.cc @@ -0,0 +1,43 @@ +/* + 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 "ipranges_helper.h" +#include "tscpp/util/TextView.h" + +#include + +bool +ipRangesHelper::addIpRanges(const std::string &s) +{ + ts::TextView src{s}; + + while (src) { + IpAddr start, end; + + if (TS_SUCCESS == ats_ip_range_parse(src.take_prefix_at(','), start, end)) { + _ipRanges.mark(start, end); + } + } + + if (_ipRanges.count() > 0) { + TSDebug(PLUGIN_NAME, " Added %zu IP ranges while parsing", _ipRanges.count()); + return true; + } else { + TSDebug(PLUGIN_NAME, " No IP ranges added, possibly bad input"); + return false; + } +} diff --git a/plugins/header_rewrite/ipranges_helper.h b/plugins/header_rewrite/ipranges_helper.h new file mode 100644 index 00000000000..5f3c5a6bdef --- /dev/null +++ b/plugins/header_rewrite/ipranges_helper.h @@ -0,0 +1,46 @@ +/* + 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 "tscore/IpMap.h" +#include "tscore/ink_inet.h" + +#include "ts/ts.h" + +#include "lulu.h" + +#include + +class ipRangesHelper +{ +public: + ~ipRangesHelper() {} + + bool addIpRanges(const std::string &s); + + bool + ipRangesMatch(const sockaddr *addr) const + { + void *ptr = nullptr; + + return _ipRanges.contains(addr, &ptr); + } + +private: + IpMap _ipRanges; +}; diff --git a/plugins/header_rewrite/matcher.h b/plugins/header_rewrite/matcher.h index 2e524d97934..45787b18ad7 100644 --- a/plugins/header_rewrite/matcher.h +++ b/plugins/header_rewrite/matcher.h @@ -28,6 +28,7 @@ #include "ts/ts.h" #include "regex_helper.h" +#include "ipranges_helper.h" #include "lulu.h" // Possible operators that we support (at least partially) @@ -36,6 +37,7 @@ enum MatcherOps { MATCH_LESS_THEN, MATCH_GREATER_THEN, MATCH_REGULAR_EXPRESSION, + MATCH_IP_RANGES, }; /////////////////////////////////////////////////////////////////////////////// @@ -51,6 +53,12 @@ class Matcher Matcher(const Matcher &) = delete; void operator=(const Matcher &) = delete; + MatcherOps + op() const + { + return _op; + } + protected: const MatcherOps _op; }; @@ -70,7 +78,7 @@ template class Matchers : public Matcher void setRegex(const std::string & /* data ATS_UNUSED */) { - if (!helper.setRegexMatch(_data)) { + if (!reHelper.setRegexMatch(_data)) { std::stringstream ss; ss << _data; TSError("[%s] Invalid regex: failed to precompile: %s", PLUGIN_NAME, ss.str().c_str()); @@ -118,6 +126,11 @@ template class Matchers : public Matcher case MATCH_REGULAR_EXPRESSION: return test_reg(t); break; + case MATCH_IP_RANGES: + // This is an error, the Matcher doesn't make sense to match on IP ranges + TSError("[%s] Invalid matcher: MATCH_IP_RANGES", PLUGIN_NAME); + throw std::runtime_error("Can not match on IP ranges"); + break; default: // ToDo: error break; @@ -189,7 +202,7 @@ template class Matchers : public Matcher int ovector[OVECCOUNT]; TSDebug(PLUGIN_NAME, "Test regular expression %s : %s", _data.c_str(), t.c_str()); - if (helper.regexMatch(t.c_str(), t.length(), ovector) > 0) { + if (reHelper.regexMatch(t.c_str(), t.length(), ovector) > 0) { TSDebug(PLUGIN_NAME, "Successfully found regular expression match"); return true; } @@ -197,5 +210,43 @@ template class Matchers : public Matcher } T _data; - regexHelper helper; + regexHelper reHelper; +}; + +// Specialized case matcher for the IP addresses matches. +// ToDo: we should specialize the regex matcher as well. +template <> class Matchers : public Matcher +{ +public: + explicit Matchers(const MatcherOps op) : Matcher(op) {} + + void + set(const std::string &data) + { + if (!ipHelper.addIpRanges(data)) { + TSError("[%s] Invalid IP-range: failed to parse: %s", PLUGIN_NAME, data.c_str()); + TSDebug(PLUGIN_NAME, "Invalid IP-range: failed to parse: %s", data.c_str()); + throw std::runtime_error("Malformed IP-range"); + } else { + TSDebug(PLUGIN_NAME, "IP-range precompiled successfully"); + } + } + + bool + test(const sockaddr *addr) const + { + if (ipHelper.ipRangesMatch(addr) > 0) { + if (TSIsDebugTagSet(PLUGIN_NAME)) { + char text[INET6_ADDRSTRLEN]; + + TSDebug(PLUGIN_NAME, "Successfully found IP-range match on %s", getIP(addr, text)); + } + return true; + } else { + return false; + } + } + +private: + ipRangesHelper ipHelper; }; diff --git a/plugins/header_rewrite/ruleset.cc b/plugins/header_rewrite/ruleset.cc index b2b7d8c1e71..c172051024c 100644 --- a/plugins/header_rewrite/ruleset.cc +++ b/plugins/header_rewrite/ruleset.cc @@ -46,7 +46,7 @@ RuleSet::add_condition(Parser &p, const char *filename, int lineno) Condition *c = condition_factory(p.get_op()); if (nullptr != c) { - TSDebug(PLUGIN_NAME, " Adding condition: %%{%s} with arg: %s", p.get_op().c_str(), p.get_arg().c_str()); + TSDebug(PLUGIN_NAME, " Adding condition: %%{%s} with arg: %s", p.get_op().c_str(), p.get_arg().c_str()); c->initialize(p); if (!c->set_hook(_hook)) { delete c; @@ -76,7 +76,7 @@ RuleSet::add_operator(Parser &p, const char *filename, int lineno) Operator *o = operator_factory(p.get_op()); if (nullptr != o) { - TSDebug(PLUGIN_NAME, " Adding operator: %s(%s)=\"%s\"", p.get_op().c_str(), p.get_arg().c_str(), p.get_value().c_str()); + TSDebug(PLUGIN_NAME, " Adding operator: %s(%s)=\"%s\"", p.get_op().c_str(), p.get_arg().c_str(), p.get_value().c_str()); o->initialize(p); if (!o->set_hook(_hook)) { delete o;