You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2022/08/31 17:49:11 UTC

[trafficserver] branch master updated: Adds efficient IP range matching to HRW conditions (#9031)

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 c58edbb6b Adds efficient IP range matching to HRW conditions (#9031)
c58edbb6b is described below

commit c58edbb6bcdd6383605721b7d457067b1a62faee
Author: Leif Hedstrom <zw...@apache.org>
AuthorDate: Wed Aug 31 11:49:05 2022 -0600

    Adds efficient IP range matching to HRW conditions (#9031)
    
    * Adds efficient IP range matching to HRW conditions
    
    * Changes to use the native TextView parser
    
    * Fixes typo in docs
    
    * Undo the hack around macos builds failing
    
    * Adds failure checks from IP range parsing
---
 doc/admin-guide/plugins/header_rewrite.en.rst | 15 +++++
 plugins/header_rewrite/Makefile.inc           |  2 +
 plugins/header_rewrite/condition.cc           |  4 ++
 plugins/header_rewrite/conditions.cc          | 95 ++++++++++++++++++++++-----
 plugins/header_rewrite/conditions.h           |  6 +-
 plugins/header_rewrite/ipranges_helper.cc     | 43 ++++++++++++
 plugins/header_rewrite/ipranges_helper.h      | 46 +++++++++++++
 plugins/header_rewrite/matcher.h              | 57 +++++++++++++++-
 plugins/header_rewrite/ruleset.cc             |  4 +-
 9 files changed, 249 insertions(+), 23 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst b/doc/admin-guide/plugins/header_rewrite.en.rst
index 554228a5d..4d331cf35 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 a64fffa93..a74c2d6dc 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 f407778ed..717742d8c 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 fa0e092ed..3f7ebca11 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<const Matchers<std::string> *>(_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<const Matchers<const sockaddr *> *>(_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<const Matchers<std::string> *>(_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<const Matchers<std::string> *>(_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<const Matchers<const sockaddr *> *>(_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<const Matchers<std::string> *>(_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 323a5bace..aedac2168 100644
--- a/plugins/header_rewrite/conditions.h
+++ b/plugins/header_rewrite/conditions.h
@@ -348,6 +348,7 @@ protected:
 class ConditionIp : public Condition
 {
   typedef Matchers<std::string> MatcherType;
+  typedef Matchers<const sockaddr *> MatcherTypeIp;
 
 public:
   explicit ConditionIp() { TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for ConditionIp"); };
@@ -503,8 +504,9 @@ private:
 /// Information about the inbound (client) session.
 class ConditionInbound : public Condition
 {
-  using MatcherType = Matchers<std::string>;
-  using self        = ConditionInbound;
+  using MatcherType   = Matchers<std::string>;
+  using MatcherTypeIp = Matchers<const sockaddr *>;
+  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 000000000..35ee9ded4
--- /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 <vector>
+
+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 000000000..5f3c5a6bd
--- /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 <string>
+
+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 2e524d979..45787b18a 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 @@ public:
   Matcher(const Matcher &) = delete;
   void operator=(const Matcher &) = delete;
 
+  MatcherOps
+  op() const
+  {
+    return _op;
+  }
+
 protected:
   const MatcherOps _op;
 };
@@ -70,7 +78,7 @@ public:
   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 @@ public:
     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 @@ private:
     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 @@ private:
   }
 
   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<const sockaddr *> : public Matcher
+{
+public:
+  explicit Matchers<const sockaddr *>(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 b2b7d8c1e..c17205102 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;