You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2022/12/04 04:24:35 UTC

[trafficserver] branch 10-Dev updated: libswoc: update IPAllow. (#9193)

This is an automated email from the ASF dual-hosted git repository.

amc pushed a commit to branch 10-Dev
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/10-Dev by this push:
     new 74dd418ea libswoc: update IPAllow. (#9193)
74dd418ea is described below

commit 74dd418ea35cdae723fe17f7a3d84db51418e668
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Sat Dec 3 22:24:28 2022 -0600

    libswoc: update IPAllow. (#9193)
---
 proxy/IPAllow.cc | 589 ++++++++++++++++++++-----------------------------------
 proxy/IPAllow.h  | 115 +++++++----
 2 files changed, 289 insertions(+), 415 deletions(-)

diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc
index 33917af14..f0d3bd7e4 100644
--- a/proxy/IPAllow.cc
+++ b/proxy/IPAllow.cc
@@ -25,83 +25,40 @@
  */
 
 #include <sstream>
+
 #include "IPAllow.h"
-#include "tscore/BufferWriter.h"
-#include "tscore/ts_file.h"
-#include "tscore/ink_memory.h"
 #include "tscore/Filenames.h"
+#include "tscpp/util/ts_errata.h"
 
-#include "yaml-cpp/yaml.h"
-
-using ts::TextView;
+#include "swoc/Vectray.h"
+#include "swoc/BufferWriter.h"
+#include "swoc/bwf_std.h"
+#include "swoc/bwf_ex.h"
+#include "swoc/bwf_ip.h"
 
-namespace
-{
-void
-SignalError(ts::BufferWriter &w, bool &flag)
-{
-  if (!flag) {
-    flag = true;
-  }
-  Error("%s", w.data());
-}
+#include "yaml-cpp/yaml.h"
 
-template <typename... Args>
-void
-ParseError(ts::TextView fmt, Args &&... args)
-{
-  ts::LocalBufferWriter<1024> w;
-  w.printv(fmt, std::forward_as_tuple(args...));
-  w.write('\0');
-  Warning("%s", w.data());
-}
+using swoc::TextView;
 
-} // namespace
+using swoc::BufferWriter;
+using swoc::bwf::Spec;
 
-namespace ts
+namespace swoc
 {
 BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, IpAllow const *obj)
+bwformat(BufferWriter &w, Spec const &spec, IpAllow const *obj)
 {
   return w.print("{}[{}]", obj->MODULE_NAME, obj->get_config_file().c_str());
 }
 
+// This needs to be in namespace "swoc" or "YAML" or ADL doesn't find the overload.
 BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, YAML::Mark const &mark)
+bwformat(BufferWriter &w, Spec const &spec, YAML::Mark const &mark)
 {
   return w.print("Line {}", mark.line);
 }
 
-BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, std::error_code const &ec)
-{
-  return w.print("[{}:{}]", ec.value(), ec.message());
-}
-
-} // namespace ts
-
-namespace YAML
-{
-template <> struct convert<ts::TextView> {
-  static Node
-  encode(ts::TextView const &tv)
-  {
-    Node zret;
-    zret = std::string(tv.data(), tv.size());
-    return zret;
-  }
-  static bool
-  decode(const Node &node, ts::TextView &tv)
-  {
-    if (!node.IsScalar()) {
-      return false;
-    }
-    tv.assign(node.Scalar());
-    return true;
-  }
-};
-
-} // namespace YAML
+} // namespace swoc
 
 enum AclOp {
   ACL_OP_ALLOW, ///< Allow access.
@@ -119,6 +76,15 @@ static ConfigUpdateHandler<IpAllow> *ipAllowUpdate;
 //
 //   Begin API functions
 //
+swoc::TextView
+IpAllow::localize(swoc::TextView src)
+{
+  auto span = _arena.alloc(src.size() + 1).rebind<char>(); // always make a C-str if copying.
+  memcpy(span.data(), src.data(), src.size());
+  span[src.size()] = '\0';
+  return span.remove_suffix(1); // don't put the extra terminating nul in the view.
+}
+
 void
 IpAllow::startup()
 {
@@ -144,10 +110,12 @@ IpAllow::reconfigure()
 
   Note("%s loading ...", ts::filename::IP_ALLOW);
 
-  new_table     = new self_type("proxy.config.cache.ip_allow.filename");
-  int retStatus = new_table->BuildTable();
-  if (retStatus) {
-    Error("%s failed to load", ts::filename::IP_ALLOW);
+  new_table   = new self_type("proxy.config.cache.ip_allow.filename");
+  auto errata = new_table->BuildTable();
+  if (!errata.is_ok()) {
+    std::string text;
+    swoc::bwprint(text, "{} failed to load\n{}", ts::filename::IP_ALLOW, errata);
+    Error("%s", text.c_str());
     delete new_table;
   } else {
     configid = configProcessor.set(configid, new_table);
@@ -174,27 +142,30 @@ IpAllow::release()
 }
 
 IpAllow::ACL
-IpAllow::match(sockaddr const *ip, match_key_t key)
+IpAllow::match(swoc::IPAddr const &addr, match_key_t key)
 {
-  self_type *self = acquire();
-  void *raw       = nullptr;
+  self_type *self      = acquire();
+  Record const *record = nullptr;
   if (SRC_ADDR == key) {
-    self->_src_map.contains(ip, &raw);
-    Record *r = static_cast<Record *>(raw);
-    // Special check - if checking in accept is enabled and the record is a deny all,
-    // then return a missing record instead to force an immediate deny. Otherwise it's delayed
-    // until after remap, to allow remap rules to tweak the result.
-    if (raw && r->_method_mask == 0 && r->_nonstandard_methods.empty() && accept_check_p) {
-      raw = nullptr;
+    if (auto spot = self->_src_map.find(addr); spot != self->_src_map.end()) {
+      auto r = std::get<1>(*spot);
+      // Special case - if checking in accept is enabled and the record is a deny all,
+      // then return a missing record instead to force an immediate deny. Otherwise it's delayed
+      // until after remap, to allow remap rules to tweak the result.
+      if (!(accept_check_p && r->_method_mask == 0 && r->_nonstandard_methods.empty())) {
+        record = r;
+      }
     }
-  } else {
-    self->_dst_map.contains(ip, &raw);
+  } else if (auto spot = self->_dst_map.find(addr); spot != self->_dst_map.end()) {
+    record = std::get<1>(*spot);
   }
-  if (raw == nullptr) {
-    self->release();
-    self = nullptr;
+
+  if (record == nullptr) {
+    self->release(); // no record, don't keep a reference to the config.
+    return {};
   }
-  return ACL{static_cast<Record *>(raw), self};
+
+  return ACL{record, self}; // Note this keeps the config in memory.
 }
 
 //
@@ -203,400 +174,272 @@ IpAllow::match(sockaddr const *ip, match_key_t key)
 
 IpAllow::IpAllow(const char *config_var) : config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {}
 
-void
-IpAllow::PrintMap(const IpMap *map) const
+BufferWriter &
+bwformat(BufferWriter &w, Spec const &spec, IpAllow::IpMap const &map)
 {
-  std::ostringstream s;
-  s << map->count() << " ACL entries.";
-  for (auto &spot : *map) {
-    char text[INET6_ADDRSTRLEN];
-    Record const *ar = static_cast<Record const *>(spot.data());
-
-    s << std::endl << "  Line " << ar->_src_line << ": " << ats_ip_ntop(spot.min(), text, sizeof text);
-    if (0 != ats_ip_addr_cmp(spot.min(), spot.max())) {
-      s << " - " << ats_ip_ntop(spot.max(), text, sizeof text);
-    }
-    s << " method=";
-    uint32_t mask = ALL_METHOD_MASK & ar->_method_mask;
-    if (ALL_METHOD_MASK == mask) {
-      s << "ALL";
+  w.print("{} entries", map.count());
+  for (auto const &spot : map) {
+    auto const *r = std::get<1>(spot);
+    w.print("\n  Line {}: {} methods=", r->_src_line, std::get<0>(spot));
+    uint32_t mask = IpAllow::ALL_METHOD_MASK & r->_method_mask;
+    if (IpAllow::ALL_METHOD_MASK == mask) {
+      w.write("ALL");
     } else if (0 == mask) {
-      s << "NONE";
+      w.write("NONE");
     } else {
       bool leader        = false; // need leading vbar?
       uint32_t test_mask = 1;     // mask for current method.
       for (int i = 0; i < HTTP_WKSIDX_METHODS_CNT; ++i, test_mask <<= 1) {
         if (mask & test_mask) {
-          if (leader) {
-            s << '|';
-          }
-          s << hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT);
+          w.print("{}{}", swoc::bwf::If(leader, "|"), hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT));
           leader = true;
         }
       }
     }
-    if (!ar->_nonstandard_methods.empty()) {
-      s << " other methods=";
+
+    if (!r->_nonstandard_methods.empty()) {
+      w.print(" {}=", r->_deny_nonstandard_methods ? IpAllow::YAML_VALUE_ACTION_ALLOW : IpAllow::YAML_VALUE_ACTION_DENY);
       bool leader = false; // need leading vbar?
-      for (const auto &_nonstandard_method : ar->_nonstandard_methods) {
-        if (leader) {
-          s << '|';
-        }
-        s << _nonstandard_method;
+      for (auto const &name : r->_nonstandard_methods) {
+        w.print("{}{}", swoc::bwf::If(leader, "|"), name);
         leader = true;
       }
     }
   }
-  Debug("ip_allow", "%s", s.str().c_str());
+  return w;
+}
+
+void
+IpAllow::DebugMap(const IpMap &map) const
+{
+  std::string out;
+  out.resize(8192);
+  swoc::bwprint(out, "{}", map);
+  Debug("ip_allow", "%s", out.c_str());
 }
 
 void
 IpAllow::Print() const
 {
   Debug("ip_allow", "Printing src map");
-  PrintMap(&_src_map);
+  DebugMap(_src_map);
   Debug("ip_allow", "Printing dest map");
-  PrintMap(&_dst_map);
+  DebugMap(_dst_map);
 }
 
-int
+swoc::Errata
 IpAllow::BuildTable()
 {
   // Table should be empty
   ink_assert(_src_map.count() == 0 && _dst_map.count() == 0);
 
   std::error_code ec;
-  std::string content{ts::file::load(config_file, ec)};
+  std::string content{swoc::file::load(config_file, ec)};
+  swoc::Errata errata;
   if (ec.value() == 0) {
-    // If it's a .yaml or the root tag is present, treat as YAML.
-    if (TextView{config_file.view()}.take_suffix_at('.') == "yaml" || std::string::npos != content.find(YAML_TAG_ROOT)) {
-      try {
-        this->YAMLBuildTable(content);
-      } catch (std::exception &ex) {
-        ParseError("{} - Invalid config: {}", this, ex.what());
-        return 1;
-      }
-    } else {
-      this->ATSBuildTable(content);
+    try {
+      errata = this->YAMLBuildTable(content);
+    } catch (std::exception &ex) {
+      return swoc::Errata(ec, ERRATA_ERROR, "{} - Invalid config: {}", this, ex.what());
+    }
+    if (!errata.is_ok()) {
+      errata.note("While parsing config file");
+      return errata;
     }
 
     if (_src_map.count() == 0 && _dst_map.count() == 0) {
-      ParseError("{} - No entries found. All IP Addresses will be blocked", this);
-      return 1;
+      return swoc::Errata(ERRATA_ERROR, "{} - No entries found. All IP Addresses will be blocked", this);
     }
 
-    // convert the coloring from indices to pointers.
-    for (auto &item : _src_map) {
-      item.setData(&_src_acls[reinterpret_cast<size_t>(item.data())]);
-    }
-    for (auto &item : _dst_map) {
-      item.setData(&_dst_acls[reinterpret_cast<size_t>(item.data())]);
-    }
     if (is_debug_tag_set("ip_allow")) {
       Print();
     }
   } else {
-    ParseError("{} Failed to load {}. All IP Addresses will be blocked", this, ec);
-    return 1;
+    return swoc::Errata(ERRATA_ERROR, "{} Failed to load {}. All IP Addresses will be blocked", this, ec);
   }
-  return 0;
+  return {};
 }
 
-bool
+swoc::Errata
 IpAllow::YAMLLoadMethod(const YAML::Node &node, Record &rec)
 {
-  const std::string &value{node.Scalar()};
+  swoc::TextView value{node.Scalar()};
+  swoc::Vectray<swoc::TextView, 8> names;
+  // Process a single token. Required to deal with the variable number of tokens.
+  auto parse_method = [&](swoc::TextView value) -> void {
+    if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
+      rec._method_mask = ALL_METHOD_MASK;
+    } else {
+      int method_idx = hdrtoken_tokenize(value.data(), value.size());
+      if (HTTP_WKSIDX_CONNECT <= method_idx && method_idx < HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
+        rec._method_mask |= ACL::MethodIdxToMask(method_idx);
+      } else {
+        names.push_back(value);
+        Debug("ip_allow", "Found nonstandard method '%.*s' at line %d", int(value.size()), value.data(), node.Mark().line);
+      }
+    }
+  };
 
-  if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
-    rec._method_mask = ALL_METHOD_MASK;
+  if (node.IsScalar()) {
+    parse_method(swoc::TextView(node.Scalar()));
+  } else if (node.IsSequence()) {
+    for (auto const &elt : node) {
+      if (elt.IsScalar()) {
+        parse_method(swoc::TextView(elt.Scalar()));
+        if (rec._method_mask == ALL_METHOD_MASK) {
+          break; // we're done here, nothing else matters.
+        }
+      } else {
+        return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(),
+                            YAML_TAG_METHODS);
+      }
+    }
   } else {
-    int method_idx = hdrtoken_tokenize(value.data(), value.size());
-    if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
-      rec._nonstandard_methods.push_back(value);
-      Debug("ip_allow", "Found nonstandard method '%s' at line %d", value.c_str(), node.Mark().line);
-    } else { // valid method.
-      rec._method_mask |= ACL::MethodIdxToMask(method_idx);
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this,
+                        node.Mark(), YAML_TAG_METHODS);
+  }
+
+  // copy over to local memory if it's not all methods and there are non-standard ones.
+  if (rec._method_mask != ALL_METHOD_MASK && !names.empty()) {
+    rec._nonstandard_methods = _arena.alloc_span<swoc::TextView>(names.size());
+    for (unsigned idx = 0; idx < names.size(); ++idx) {
+      rec._nonstandard_methods[idx] = this->localize(names[idx]);
     }
   }
-  return true;
+  return {};
 }
 
-bool
-IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, void *mark)
+swoc::Errata
+IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, IpAllow::Record const *record)
 {
-  if (node.IsScalar()) {
-    IpAddr min, max;
-    if (0 == ats_ip_range_parse(node.Scalar(), min, max)) {
-      map->fill(min, max, mark);
-      return true;
-    } else {
-      ParseError("{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
-    }
+  if (!node.IsScalar()) {
+    return swoc::Errata(ERRATA_ERROR, "{} Expected IP address range at {}, found non-literal.", this, node.Mark());
+  }
+
+  swoc::TextView debug(node.Scalar());
+  (void)debug;
+  if (swoc::IPRange r; r.load(node.Scalar())) {
+    map->fill(r, record);
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
   }
-  return false;
+  return {};
 }
 
-bool
+swoc::Errata
 IpAllow::YAMLLoadEntry(const YAML::Node &entry)
 {
   AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler.
   YAML::Node node;
-  IpAddr min, max;
-  std::string value;
-  Record rec;
-  std::vector<Record> *acls{nullptr};
-  IpMap *map = nullptr;
+  auto record = _arena.make<Record>();
+  IpMap *map  = nullptr; // src or dst map.
 
   if (!entry.IsMap()) {
-    ParseError("{} {} - ACL items must be maps.", this, entry.Mark());
-    return false;
+    return swoc::Errata(ERRATA_ERROR, "{} {} - ACL items must be maps.", this, entry.Mark());
   }
 
-  if (entry[YAML_TAG_APPLY]) {
-    auto apply_node{entry[YAML_TAG_APPLY]};
+  if (YAML::Node apply_node{entry[YAML_TAG_APPLY]}; apply_node) {
     if (apply_node.IsScalar()) {
-      ts::TextView value{apply_node.Scalar()};
+      swoc::TextView value{apply_node.Scalar()};
       if (0 == strcasecmp(value, YAML_VALUE_APPLY_IN)) {
-        acls = &_src_acls;
-        map  = &_src_map;
+        map = &_src_map;
       } else if (0 == strcasecmp(value, YAML_VALUE_APPLY_OUT)) {
-        acls = &_dst_acls;
-        map  = &_dst_map;
+        map = &_dst_map;
       } else {
-        ParseError(R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
-                   YAML_VALUE_APPLY_OUT);
-        return false;
+        return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(),
+                            YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT);
       }
     } else {
-      ParseError(R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
-                 YAML_VALUE_APPLY_OUT);
-      return false;
+      return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(),
+                          YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT);
     }
   } else {
-    ParseError(R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY);
-    return false;
+    return swoc::Errata(ERRATA_ERROR, R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY);
   }
 
-  void *ipmap_mark = reinterpret_cast<void *>(acls->size());
-  if (entry[YAML_TAG_IP_ADDRS]) {
-    auto addr_node{entry[YAML_TAG_IP_ADDRS]};
-    if (addr_node.IsSequence()) {
-      for (auto const &n : addr_node) {
-        if (!this->YAMLLoadIPAddrRange(n, map, ipmap_mark)) {
-          return false;
-        }
+  if (node = entry[YAML_TAG_ACTION]; node) {
+    if (node.IsScalar()) {
+      swoc::TextView value(node.Scalar());
+      if (value == YAML_VALUE_ACTION_ALLOW) {
+        op = ACL_OP_ALLOW;
+      } else if (value == YAML_VALUE_ACTION_DENY) {
+        op = ACL_OP_DENY;
+      } else {
+        return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(),
+                            YAML_TAG_ACTION, YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
       }
-    } else if (!this->YAMLLoadIPAddrRange(addr_node, map, ipmap_mark)) {
-      return false;
+    } else {
+      return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(),
+                          YAML_TAG_ACTION);
     }
-  }
-
-  if (!entry[YAML_TAG_ACTION]) {
-    ParseError("{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
-    return false;
-  }
-
-  node = entry[YAML_TAG_ACTION];
-  if (!node.IsScalar()) {
-    ParseError("{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(), YAML_TAG_ACTION);
-    return false;
-  }
-  value = node.as<std::string>();
-  if (value == YAML_VALUE_ACTION_ALLOW) {
-    op = ACL_OP_ALLOW;
-  } else if (value == YAML_VALUE_ACTION_DENY) {
-    op = ACL_OP_DENY;
   } else {
-    ParseError("{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(), YAML_TAG_ACTION,
-               YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
-    return false;
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
   }
-  if (!entry[YAML_TAG_METHODS]) {
-    rec._method_mask = ALL_METHOD_MASK;
-  } else {
-    node = entry[YAML_TAG_METHODS];
-    if (node.IsScalar()) {
-      this->YAMLLoadMethod(node, rec);
-    } else if (node.IsSequence()) {
-      for (auto const &elt : node) {
-        if (elt.IsScalar()) {
-          this->YAMLLoadMethod(elt, rec);
-          if (rec._method_mask == ALL_METHOD_MASK) {
-            break; // we're done here, nothing else matters.
-          }
+
+  if (YAML::Node addr_node = entry[YAML_TAG_IP_ADDRS]; addr_node) {
+    bool marked_p = false;
+    if (addr_node.IsSequence()) {
+      for (auto const &n : addr_node) {
+        if (auto errata = this->YAMLLoadIPAddrRange(n, map, record); errata.is_ok()) {
+          marked_p = true;
         } else {
-          ParseError("{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(), YAML_TAG_METHODS);
-          return false;
+          errata.note(R"(In record at {})", entry.Mark());
+          return errata;
         }
       }
     } else {
-      ParseError("{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this, node.Mark(),
-                 YAML_TAG_METHODS);
+      if (auto errata = this->YAMLLoadIPAddrRange(addr_node, map, record); errata.is_ok()) {
+        marked_p = true;
+      } else {
+        errata.note(R"(In record at {})", entry.Mark());
+        return errata;
+      }
+    }
+    if (!marked_p) {
+      return swoc::Errata(ERRATA_ERROR, "No valid addresses for rule at {}", node.Mark());
     }
+  } else {
+    return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_IP_ADDRS);
   }
+
+  if (node = entry[YAML_TAG_METHODS]; node) {
+    if (auto errata = this->YAMLLoadMethod(node, *record); !errata.is_ok()) {
+      return errata;
+    }
+  } else {
+    record->_method_mask = ALL_METHOD_MASK;
+  }
+
   if (op == ACL_OP_DENY) {
-    rec._method_mask              = ALL_METHOD_MASK & ~rec._method_mask;
-    rec._deny_nonstandard_methods = true;
+    record->_method_mask              = ALL_METHOD_MASK & ~record->_method_mask;
+    record->_deny_nonstandard_methods = true;
   }
-  rec._src_line = entry.Mark().line;
-  // If we get here, everything parsed OK, add the record.
-  acls->emplace_back(std::move(rec));
-  return true;
+
+  record->_src_line = entry.Mark().line;
+  return {};
 }
 
-int
+swoc::Errata
 IpAllow::YAMLBuildTable(std::string const &content)
 {
   YAML::Node root{YAML::Load(content)};
   if (!root.IsMap()) {
-    ParseError("{} - top level object was not a map. All IP Addresses will be blocked", this);
-    return 1;
+    return swoc::Errata("{} - top level object was not a map. All IP Addresses will be blocked", this);
   }
 
-  YAML::Node data{root[YAML_TAG_ROOT]};
+  YAML::Node data{root[YAML_TAG_ROOT.data()]};
   if (!data) {
-    ParseError("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
+    return swoc::Errata("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
   } else if (data.IsSequence()) {
     for (auto const &entry : data) {
-      if (!this->YAMLLoadEntry(entry)) {
-        return 1;
+      if (auto errata = this->YAMLLoadEntry(entry); !errata.is_ok()) {
+        return errata;
       }
     }
   } else if (data.IsMap()) {
-    this->YAMLLoadEntry(data); // singleton, just load it.
+    return this->YAMLLoadEntry(data); // singleton, just load it.
   } else {
-    ParseError("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
-    return 1;
-  }
-  return 0;
-}
-
-int
-IpAllow::ATSBuildTable(std::string const &content)
-{
-  int line_num = 0;
-  IpAddr addr1;
-  IpAddr addr2;
-  bool alarmAlready = false;
-  ts::LocalBufferWriter<1024> bw_err;
-
-  TextView src(content);
-  TextView line;
-  auto err_prefix = [&]() -> ts::BufferWriter & {
-    return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file.c_str(), line_num);
-  };
-
-  while (!(line = src.take_prefix_at('\n')).empty()) {
-    ++line_num;
-    line.trim_if(&isspace);
-
-    if (!line.empty() && *line != '#') {
-      TextView token = line.take_prefix_if(&isspace);
-      TextView value = token.split_suffix_at('=');
-      match_key_t match;
-      if (value.empty()) {
-        err_prefix().print("No value found in token '{}'.\0", token);
-        SignalError(bw_err, alarmAlready);
-        continue;
-      } else if (strcasecmp(token, OPT_MATCH_SRC) == 0) {
-        match = SRC_ADDR;
-      } else if (strcasecmp(token, OPT_MATCH_DST) == 0) {
-        match = DST_ADDR;
-      } else {
-        err_prefix().print("'{}' is not a valid key.\0", token);
-        SignalError(bw_err, alarmAlready);
-        continue;
-      }
-
-      if (0 == ats_ip_range_parse(value, addr1, addr2)) {
-        uint32_t acl_method_mask      = 0;
-        bool op_found_p               = false;
-        bool method_found_p           = false;
-        bool all_found_p              = false;
-        bool deny_nonstandard_methods = false;
-        bool line_valid_p             = true;
-        AclOp op                      = ACL_OP_DENY; // "shut up", I explained to the compiler.
-        MethodNames nonstandard_methods;
-
-        while (line_valid_p && !line.ltrim_if(&isspace).empty()) {
-          token = line.take_prefix_if(&isspace);
-          value = token.split_suffix_at('=');
-
-          if (value.empty()) {
-            err_prefix().print("No value found in token '{}'\0", token);
-            SignalError(bw_err, alarmAlready);
-            line_valid_p = false;
-          } else if (strcasecmp(token, OPT_ACTION_TAG) == 0) {
-            if (strcasecmp(value, OPT_ACTION_ALLOW) == 0) {
-              op_found_p = true, op = ACL_OP_ALLOW;
-            } else if (strcasecmp(value, OPT_ACTION_DENY) == 0) {
-              op_found_p = true, op = ACL_OP_DENY;
-            } else {
-              err_prefix().print("'{}' is not a valid action\0", value);
-              SignalError(bw_err, alarmAlready);
-              line_valid_p = false;
-            }
-          } else if (strcasecmp(token, OPT_METHOD) == 0) {
-            // Parse method="GET|HEAD"
-            while (!value.empty()) {
-              TextView method_name = value.take_prefix_at('|');
-              if (strcasecmp(method_name, OPT_METHOD_ALL) == 0) {
-                all_found_p = true;
-                break;
-              } else {
-                int method_idx = hdrtoken_tokenize(method_name.data(), method_name.size());
-                if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
-                  nonstandard_methods.emplace_back(std::string(method_name.data(), method_name.size()));
-                  Debug("ip_allow", "%s",
-                        bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data());
-                } else { // valid method.
-                  acl_method_mask |= ACL::MethodIdxToMask(method_idx);
-                }
-                method_found_p = true;
-              }
-            }
-          } else {
-            err_prefix().print("'{}' is not a valid token\0", token);
-            SignalError(bw_err, alarmAlready);
-            line_valid_p = false;
-          }
-        }
-        if (!line_valid_p) {
-          continue; // error parsing the line, go on to the next.
-        }
-        if (!op_found_p) {
-          err_prefix().print("No action found.\0");
-          SignalError(bw_err, alarmAlready);
-          continue;
-        }
-        // If method not specified, default to ALL
-        if (all_found_p || !method_found_p) {
-          method_found_p  = true;
-          acl_method_mask = ALL_METHOD_MASK;
-          nonstandard_methods.clear();
-        }
-        // When deny, use bitwise complement.  (Make the rule 'allow for all
-        // methods except those specified')
-        if (op == ACL_OP_DENY) {
-          acl_method_mask          = ALL_METHOD_MASK & ~acl_method_mask;
-          deny_nonstandard_methods = true;
-        }
-
-        if (method_found_p) {
-          std::vector<Record> &acls = match == DST_ADDR ? _dst_acls : _src_acls;
-          IpMap &map                = match == DST_ADDR ? _dst_map : _src_map;
-          acls.emplace_back(acl_method_mask, line_num, std::move(nonstandard_methods), deny_nonstandard_methods);
-          // Color with index in acls because at this point the address is volatile.
-          map.fill(addr1, addr2, reinterpret_cast<void *>(acls.size() - 1));
-        } else {
-          err_prefix().print("No valid method found\0"); // changed by YTS Team, yamsat bug id -59022
-          SignalError(bw_err, alarmAlready);
-        }
-      } else {
-        err_prefix().print("'{}' is not a valid IP address range\0", value);
-        SignalError(bw_err, alarmAlready);
-      }
-    }
+    return swoc::Errata("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
   }
-  return 0;
+  return {};
 }
diff --git a/proxy/IPAllow.h b/proxy/IPAllow.h
index 9221e7525..4dd9053fa 100644
--- a/proxy/IPAllow.h
+++ b/proxy/IPAllow.h
@@ -36,9 +36,10 @@
 
 #include "hdrs/HTTP.h"
 #include "ProxyConfig.h"
-#include "tscore/IpMap.h"
-#include "tscpp/util/TextView.h"
-#include "tscore/ts_file.h"
+#include "swoc/TextView.h"
+#include "swoc/swoc_file.h"
+#include "swoc/swoc_ip.h"
+#include "swoc/Errata.h"
 
 // forward declare in name only so it can be a friend.
 struct IpAllowUpdate;
@@ -53,7 +54,7 @@ class IpAllow : public ConfigInfo
 {
   friend struct IpAllowUpdate;
 
-  using MethodNames = std::vector<std::string>;
+  using MethodNames = swoc::MemSpan<swoc::TextView>;
 
   static constexpr uint32_t ALL_METHOD_MASK = ~0; // Mask for all methods.
 
@@ -65,40 +66,54 @@ class IpAllow : public ConfigInfo
     /// Present only to make Vec<> happy, do not use.
     Record()              = default;
     Record(Record &&that) = default;
+    /** Construct from mask.
+     *
+     * @param method_mask Bit mask of allowed methods.
+     */
     explicit Record(uint32_t method_mask);
+
+    /** Construct from values.
+     *
+     * @param method_mask Well known method mask.
+     * @param line Source line in configuration file.
+     * @param nonstandard_methods Allowed methods that are not well known.
+     * @param deny_nonstandard_methods Denied methods that are not well known.
+     */
     Record(uint32_t method_mask, int line, MethodNames &&nonstandard_methods, bool deny_nonstandard_methods);
 
-    uint32_t _method_mask{0};
-    int _src_line{0};
-    MethodNames _nonstandard_methods;
-    bool _deny_nonstandard_methods{false};
+    uint32_t _method_mask{0};              ///< Well known method mask.
+    int _src_line{0};                      ///< Configuration file sourc line.
+    MethodNames _nonstandard_methods;      ///< Allowed methods that are not well known.
+    bool _deny_nonstandard_methods{false}; ///< Denied methods that are not well known.
   };
 
 public:
   using self_type     = IpAllow; ///< Self reference type.
   using scoped_config = ConfigProcessor::scoped_config<self_type, self_type>;
+  using IpMap         = swoc::IPSpace<Record const *>;
 
   // indicator for whether we should be checking the acl record for src ip or dest ip
   enum match_key_t { SRC_ADDR, DST_ADDR };
+
   /// Token strings for configuration
-  static constexpr ts::TextView OPT_MATCH_SRC{"src_ip"};
-  static constexpr ts::TextView OPT_MATCH_DST{"dest_ip"};
-  static constexpr ts::TextView OPT_ACTION_TAG{"action"};
-  static constexpr ts::TextView OPT_ACTION_ALLOW{"ip_allow"};
-  static constexpr ts::TextView OPT_ACTION_DENY{"ip_deny"};
-  static constexpr ts::TextView OPT_METHOD{"method"};
-  static constexpr ts::TextView OPT_METHOD_ALL{"all"};
-
-  static constexpr ts::TextView YAML_TAG_ROOT{"ip_allow"};
-  static constexpr ts::TextView YAML_TAG_IP_ADDRS{"ip_addrs"};
-  static constexpr ts::TextView YAML_TAG_APPLY{"apply"};
-  static constexpr ts::TextView YAML_VALUE_APPLY_IN{"in"};
-  static constexpr ts::TextView YAML_VALUE_APPLY_OUT{"out"};
-  static constexpr ts::TextView YAML_TAG_ACTION{"action"};
-  static constexpr ts::TextView YAML_VALUE_ACTION_ALLOW{"allow"};
-  static constexpr ts::TextView YAML_VALUE_ACTION_DENY{"deny"};
-  static constexpr ts::TextView YAML_TAG_METHODS{"methods"};
-  static constexpr ts::TextView YAML_VALUE_METHODS_ALL{"all"};
+  static constexpr swoc::TextView OPT_MATCH_SRC{"src_ip"};
+  static constexpr swoc::TextView OPT_MATCH_DST{"dest_ip"};
+  static constexpr swoc::TextView OPT_ACTION_TAG{"action"};
+  static constexpr swoc::TextView OPT_ACTION_ALLOW{"ip_allow"};
+  static constexpr swoc::TextView OPT_ACTION_DENY{"ip_deny"};
+  static constexpr swoc::TextView OPT_METHOD{"method"};
+  static constexpr swoc::TextView OPT_METHOD_ALL{"all"};
+
+  static const inline std::string YAML_TAG_ROOT{"ip_allow"};
+  static const inline std::string YAML_TAG_IP_ADDRS{"ip_addrs"};
+  static const inline std::string YAML_TAG_APPLY{"apply"};
+  static const inline std::string YAML_VALUE_APPLY_IN{"in"};
+  static const inline std::string YAML_VALUE_APPLY_OUT{"out"};
+  static const inline std::string YAML_TAG_ACTION{"action"};
+  static const inline std::string YAML_VALUE_ACTION_ALLOW{"allow"};
+  static const inline std::string YAML_VALUE_ACTION_DENY{"deny"};
+  static const inline std::string YAML_TAG_METHODS{"methods"};
+  static const inline std::string YAML_VALUE_METHODS_ALL{"all"};
 
   static constexpr const char *MODULE_NAME = "IPAllow";
 
@@ -121,6 +136,11 @@ public:
 
     void clear(); ///< Drop data and config reference.
 
+    /** Convert well known string index to mask.
+     *
+     * @param wksidx Well known string index.
+     * @return A mask for that method.
+     */
     static uint32_t MethodIdxToMask(int wksidx);
 
     /// Check if the ACL is valid (i.e. not uninitialized or missing).
@@ -149,8 +169,9 @@ public:
 
   void Print() const;
 
-  static ACL match(sockaddr const *ip, match_key_t key);
-  static ACL match(IpEndpoint const *ip, match_key_t key);
+  static ACL match(swoc::IPAddr const &addr, match_key_t key);
+  static ACL match(swoc::IPEndpoint const *addr, match_key_t key);
+  static ACL match(sockaddr const *sa, match_key_t key);
 
   static void startup();
   static void reconfigure();
@@ -180,27 +201,31 @@ public:
    */
   static bool isAcceptCheckEnabled();
 
-  const ts::file::path &get_config_file() const;
+  const swoc::file::path &get_config_file() const;
 
 private:
   static size_t configid;               ///< Configuration ID for update management.
   static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access.
   static bool accept_check_p;           ///< @c true if deny all can be enforced during accept.
 
-  void PrintMap(const IpMap *map) const;
+  void DebugMap(IpMap const &map) const;
 
-  int BuildTable();
-  int ATSBuildTable(const std::string &);
-  int YAMLBuildTable(const std::string &);
-  bool YAMLLoadEntry(const YAML::Node &);
-  bool YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, void *mark);
-  bool YAMLLoadMethod(const YAML::Node &node, Record &rec);
+  swoc::Errata BuildTable();
+  swoc::Errata YAMLBuildTable(const std::string &);
+  swoc::Errata YAMLLoadEntry(const YAML::Node &);
+  swoc::Errata YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, Record const *mark);
+  swoc::Errata YAMLLoadMethod(const YAML::Node &node, Record &rec);
 
-  ts::file::path config_file; ///< Path to configuration file.
+  /// Copy @a src to the local arena and review a view of the copy.
+  swoc::TextView localize(swoc::TextView src);
+
+  swoc::file::path config_file; ///< Path to configuration file.
   IpMap _src_map;
   IpMap _dst_map;
-  std::vector<Record> _src_acls;
-  std::vector<Record> _dst_acls;
+  /// Storage for records.
+  swoc::MemArena _arena;
+
+  friend swoc::BufferWriter &bwformat(swoc::BufferWriter &w, swoc::bwf::Spec const &spec, IpAllow::IpMap const &map);
 };
 
 // ------ Record methods --------
@@ -321,9 +346,15 @@ IpAllow::isAcceptCheckEnabled()
 }
 
 inline auto
-IpAllow::match(IpEndpoint const *ip, match_key_t key) -> ACL
+IpAllow::match(swoc::IPEndpoint const *addr, match_key_t key) -> ACL
+{
+  return self_type::match(swoc::IPAddr(&addr->sa), key);
+}
+
+inline auto
+IpAllow::match(sockaddr const *sa, match_key_t key) -> ACL
 {
-  return self_type::match(&ip->sa, key);
+  return self_type::match(swoc::IPAddr(sa), key);
 }
 
 inline auto
@@ -332,7 +363,7 @@ IpAllow::makeAllowAllACL() -> ACL
   return {&ALLOW_ALL_RECORD, nullptr};
 }
 
-inline const ts::file::path &
+inline const swoc::file::path &
 IpAllow::get_config_file() const
 {
   return config_file;