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/11/21 20:12:52 UTC

[trafficserver] branch 10-Dev updated: libswoc: Add ATS IP address utitilies on top of libswoc. (#9209)

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 8df36ae51 libswoc: Add ATS IP address utitilies on top of libswoc. (#9209)
8df36ae51 is described below

commit 8df36ae51db5536a2b88b37c364e02316c256a90
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Mon Nov 21 14:12:47 2022 -0600

    libswoc: Add ATS IP address utitilies on top of libswoc. (#9209)
    
    libswoc: Add ATS IP address utilities on top of libswoc.
---
 include/tscpp/util/Makefile.am |   1 +
 include/tscpp/util/ts_ip.h     | 450 +++++++++++++++++++++++++++++++++++++++++
 src/tscpp/util/Makefile.am     |   2 +-
 src/tscpp/util/ts_ip.cc        | 123 +++++++++++
 4 files changed, 575 insertions(+), 1 deletion(-)

diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am
index 1c9826e4c..bde4eb3d0 100644
--- a/include/tscpp/util/Makefile.am
+++ b/include/tscpp/util/Makefile.am
@@ -20,6 +20,7 @@ library_includedir=$(includedir)/tscpp/util
 
 library_include_HEADERS = \
         ts_diag_levels.h \
+        ts_ip.h \
 	IntrusiveDList.h \
 	LocalBuffer.h \
 	PostScript.h \
diff --git a/include/tscpp/util/ts_ip.h b/include/tscpp/util/ts_ip.h
new file mode 100644
index 000000000..2248b985d
--- /dev/null
+++ b/include/tscpp/util/ts_ip.h
@@ -0,0 +1,450 @@
+/** @file
+
+  IP address handling support.
+
+  Built on top of libswoc IP networking support to provide utilities specialized for ATS.
+
+  @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 <optional>
+
+#include "swoc/swoc_ip.h"
+
+namespace ts
+{
+/// Pair of addresses, each optional.
+/// Used in situations where both an IPv4 and IPv6 may be needed.
+class IPAddrPair
+{
+public:
+  using self_type = IPAddrPair;
+
+  IPAddrPair() = default; ///< Default construct empty pair.
+
+  /** Construct with IPv4 address.
+   *
+   * @param a4 Address.
+   */
+  IPAddrPair(swoc::IP4Addr a4);
+
+  /** Construct with IPv6 address.
+   *
+   * @param a6 Address.
+   */
+  IPAddrPair(swoc::IP6Addr a6);
+
+  /// @return @c true if either address is present.
+  bool has_value() const;
+
+  /// @return @c true if an IPv4 address is present.
+  bool has_ip4() const;
+
+  /// @return @c true if an IPv6 address is present.
+  bool has_ip6() const;
+
+  /// @return The IPv4 address
+  /// @note Does not check if the address is present.
+  swoc::IP4Addr const &ip4() const;
+
+  /// @return The IPv6 address
+  /// @note Does not check if the address is present.
+  swoc::IP6Addr const &ip6() const;
+
+  /** Assign the IPv4 address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP4Addr const &addr);
+
+  /** Assign the IPv6 address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP6Addr const &addr);
+
+  /** Assign an address.
+   *
+   * @param addr Address to assign.
+   * @return @a this
+   *
+   * The appropriate internal address is assigned based on the address in @a addr.
+   */
+  self_type &operator=(swoc::IPAddr const &addr);
+
+protected:
+  std::optional<swoc::IP4Addr> _ip4;
+  std::optional<swoc::IP6Addr> _ip6;
+};
+
+inline IPAddrPair::IPAddrPair(swoc::IP4Addr a4) : _ip4(a4) {}
+inline IPAddrPair::IPAddrPair(swoc::IP6Addr a6) : _ip6(a6) {}
+
+inline bool
+IPAddrPair::has_value() const
+{
+  return _ip4.has_value() || _ip6.has_value();
+}
+
+inline bool
+IPAddrPair::has_ip4() const
+{
+  return _ip4.has_value();
+}
+
+inline bool
+IPAddrPair::has_ip6() const
+{
+  return _ip6.has_value();
+}
+
+inline swoc::IP4Addr const &
+IPAddrPair::ip4() const
+{
+  return _ip4.value();
+}
+
+inline swoc::IP6Addr const &
+IPAddrPair::ip6() const
+{
+  return _ip6.value();
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IP4Addr const &addr) -> self_type &
+{
+  _ip4 = addr;
+  return *this;
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IP6Addr const &addr) -> self_type &
+{
+  _ip6 = addr;
+  return *this;
+}
+
+inline auto
+IPAddrPair::operator=(swoc::IPAddr const &addr) -> self_type &
+{
+  if (addr.is_ip4()) {
+    _ip4 = addr.ip4();
+  } else if (addr.is_ip6()) {
+    _ip6 = addr.ip6();
+  }
+
+  return *this;
+}
+
+/// Pair of services, each optional.
+/// Used in situations where both IPv4 and IPv6 may be needed.
+class IPSrvPair
+{
+  using self_type = IPSrvPair;
+
+public:
+  IPSrvPair() = default; ///< Default construct empty pair.
+
+  /** Construct from address(es) and port.
+   *
+   * @param a4 IPv4 address.
+   * @param a6 IPv6 address.
+   * @param port Port
+   *
+   * @a port is used for both service instances.
+   */
+  IPSrvPair(swoc::IP4Addr const &a4, swoc::IP6Addr const &a6, in_port_t port = 0);
+
+  /** Construct from IPv4 address and optional port.
+   *
+   * @param a4 IPv4 address
+   * @param port Port.
+   */
+  IPSrvPair(swoc::IP4Addr const &a4, in_port_t port = 0);
+
+  /** Construct from IPv6 address and optional port.
+   *
+   * @param a6 IPv6 address
+   * @param port Port.
+   */
+  IPSrvPair(swoc::IP6Addr const &a6, in_port_t port = 0);
+
+  /** Construct from an address pair and optional port.
+   *
+   * @param a Address pair.
+   * @param port port.
+   *
+   * For each family the service is instantatied only if the address is present in @a a.
+   * @a port is used for all service instances.
+   */
+  explicit IPSrvPair(IPAddrPair const &a, in_port_t port = 0);
+
+  /// @return @c true if any service is present.
+  bool has_value() const;
+
+  /// @return @c true if the IPv4 service is present.
+  bool has_ip4() const;
+
+  /// @return @c true if the the IPv6 service is present.
+  bool has_ip6() const;
+
+  /// @return The IPv4 service.
+  /// @note Does not check if the service is present.
+  swoc::IP4Srv const &ip4() const;
+
+  /// @return The IPv6 service.
+  /// @note Does not check if the service is present.
+  swoc::IP6Srv const &ip6() const;
+
+  /** Assign the IPv4 service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP4Srv const &srv);
+
+  /** Assign the IPv6 service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   */
+  self_type &operator=(swoc::IP6Srv const &srv);
+
+  /** Assign a service.
+   *
+   * @param srv Service to assign.
+   * @return @a this
+   *
+   * The assigned service is the same family as @a srv.
+   */
+  self_type &operator=(swoc::IPSrv const &srv);
+
+protected:
+  std::optional<swoc::IP4Srv> _ip4;
+  std::optional<swoc::IP6Srv> _ip6;
+};
+
+inline IPSrvPair::IPSrvPair(swoc::IP4Addr const &a4, swoc::IP6Addr const &a6, in_port_t port)
+  : _ip4(swoc::IP4Srv(a4, port)), _ip6(swoc::IP6Srv(a6, port))
+{
+}
+
+inline IPSrvPair::IPSrvPair(swoc::IP4Addr const &a4, in_port_t port) : _ip4(swoc::IP4Srv(a4, port)) {}
+
+inline IPSrvPair::IPSrvPair(swoc::IP6Addr const &a6, in_port_t port) : _ip6(swoc::IP6Srv(a6, port)) {}
+
+inline IPSrvPair::IPSrvPair(IPAddrPair const &a, in_port_t port)
+{
+  if (a.has_ip4()) {
+    _ip4 = swoc::IP4Srv(a.ip4(), port);
+  }
+
+  if (a.has_ip6()) {
+    _ip6 = swoc::IP6Srv(a.ip6(), port);
+  }
+}
+
+inline bool
+IPSrvPair::has_value() const
+{
+  return _ip4.has_value() || _ip6.has_value();
+}
+
+inline bool
+IPSrvPair::has_ip4() const
+{
+  return _ip4.has_value();
+}
+
+inline bool
+IPSrvPair::has_ip6() const
+{
+  return _ip6.has_value();
+}
+
+inline swoc::IP4Srv const &
+IPSrvPair::ip4() const
+{
+  return _ip4.value();
+}
+
+inline swoc::IP6Srv const &
+IPSrvPair::ip6() const
+{
+  return _ip6.value();
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IP4Srv const &srv) -> self_type &
+{
+  _ip4 = srv;
+  return *this;
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IP6Srv const &srv) -> self_type &
+{
+  _ip6 = srv;
+  return *this;
+}
+
+inline auto
+IPSrvPair::operator=(swoc::IPSrv const &srv) -> self_type &
+{
+  if (srv.is_ip4()) {
+    _ip4 = srv.ip4();
+  } else if (srv.is_ip6()) {
+    _ip6 = srv.ip6();
+  }
+  return *this;
+}
+
+/** Get the best address info for @a name.
+
+ * @param name Address / host.
+ * @return An address pair.
+ *
+ * If @a name is a valid IP address it is interpreted as such. Otherwise it is presumed
+ * to be a host name suitable for resolution using @c getaddrinfo. The "best" address is
+ * selected by ranking the types of addresses in the order
+ *
+ * - Global, multi-cast, non-routable (private), link local, loopback
+ *
+ * For a host name, an IPv4 and IPv6 address may be returned. The "best" is computed independently
+ * for each family.
+ *
+ * @see getaddrinfo
+ * @see ts::getbestsrvinfo
+ */
+IPAddrPair getbestaddrinfo(swoc::TextView name);
+
+/** Get the best address and port info for @a name.
+
+ * @param name Address / host.
+ * @return An address pair.
+ *
+ * If @a name is a valid IP address (with optional port) it is interpreted as such. Otherwise it is
+ * presumed to be a host name (with optional port) suitable for resolution using @c getaddrinfo. The "best" address is
+ * selected by ranking the types of addresses in the order
+ *
+ * - Global, multi-cast, non-routable (private), link local, loopback
+ *
+ * For a host name, an IPv4 and IPv6 service may be returned. The "best" is computed independently
+ * for each family. The port, if present, is the same for all returned services.
+ *
+ * @see getaddrinfo
+ * @see ts::getbestaddrinfo
+ */
+IPSrvPair getbestsrvinfo(swoc::TextView name);
+
+/** An IPSpace that contains only addresses.
+ *
+ * This is to @c IPSpace as @c std::set is to @c std::map. The @c value_type is removed from the API
+ * and only the keys are visible. This suits use cases where the goal is to track the presence of
+ * addresses without any additional data.
+ *
+ * @note Because there is only one value stored, there is no difference between @c mark and @c fill.
+ */
+class IPAddrSet
+{
+  using self_type = IPAddrSet;
+
+public:
+  /// Default construct empty set.
+  IPAddrSet() = default;
+
+  /** Add addresses to the set.
+   *
+   * @param r Range of addresses to add.
+   * @return @a this
+   *
+   * Identical to @c fill.
+   */
+  self_type &mark(swoc::IPRange const &r);
+
+  /** Add addresses to the set.
+   *
+   * @param r Range of addresses to add.
+   * @return @a this
+   *
+   * Identical to @c mark.
+   */
+  self_type &fill(swoc::IPRange const &r);
+
+  /// @return @c true if @a addr is in the set.
+  bool contains(swoc::IPAddr const &addr) const;
+
+  /// @return Number of ranges in the set.
+  size_t count() const;
+
+protected:
+  /// Empty struct to use for payload.
+  /// This declares the struct and defines the singleton instance used.
+  static inline constexpr struct Mark {
+    using self_type = Mark;
+    /// @internal @c IPSpace requires equality / inequality operators.
+    /// These make all instance equal to each other.
+    bool operator==(self_type const &that);
+    bool operator!=(self_type const &that);
+  } MARK{};
+
+  /// The address set.
+  swoc::IPSpace<Mark> _addrs;
+};
+
+inline auto
+IPAddrSet::mark(swoc::IPRange const &r) -> self_type &
+{
+  _addrs.mark(r, MARK);
+  return *this;
+}
+
+inline auto
+IPAddrSet::fill(swoc::IPRange const &r) -> self_type &
+{
+  _addrs.mark(r, MARK);
+  return *this;
+}
+
+inline bool
+IPAddrSet::contains(swoc::IPAddr const &addr) const
+{
+  return _addrs.find(addr) != _addrs.end();
+}
+
+inline size_t
+IPAddrSet::count() const
+{
+  return _addrs.count();
+}
+
+inline bool
+IPAddrSet::Mark::operator==(IPAddrSet::Mark::self_type const &that)
+{
+  return true;
+}
+
+inline bool
+IPAddrSet::Mark::operator!=(IPAddrSet::Mark::self_type const &that)
+{
+  return false;
+}
+
+} // namespace ts
diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am
index d018e4956..66dffc2cc 100644
--- a/src/tscpp/util/Makefile.am
+++ b/src/tscpp/util/Makefile.am
@@ -30,7 +30,7 @@ libtscpputil_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info @TS_LIBTOOL_V
 libtscpputil_la_LIBADD = @SWOC_LIBS@
 
 libtscpputil_la_SOURCES = \
-	ts_diags.cc TextView.cc
+	ts_diags.cc ts_ip.cc TextView.cc
 
 test_tscpputil_CPPFLAGS = $(AM_CPPFLAGS)\
 	-I$(abs_top_srcdir)/tests/include @SWOC_INCLUDES@
diff --git a/src/tscpp/util/ts_ip.cc b/src/tscpp/util/ts_ip.cc
new file mode 100644
index 000000000..01e8ae8dc
--- /dev/null
+++ b/src/tscpp/util/ts_ip.cc
@@ -0,0 +1,123 @@
+/** @file
+
+    IP address handling support.
+
+    @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 <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <tscore/ink_memory.h>
+
+#include "tscpp/util/ts_ip.h"
+
+namespace ts
+{
+IPAddrPair
+getbestaddrinfo(swoc::TextView name)
+{
+  // If @a name parses as a valid address, return it as that address.
+
+  if (swoc::IP4Addr addr; addr.load(name)) {
+    return addr;
+  }
+
+  if (swoc::IP6Addr addr; addr.load(name)) {
+    return addr;
+  }
+
+  // Presume it is a host name, make a copy to guarantee C string.
+  char *tmp = static_cast<char *>(alloca(name.size() + 1));
+  memcpy(tmp, name.data(), name.size());
+  tmp[name.size()] = 0;
+  name.assign(tmp, name.size());
+
+  // List of address types, in order of worst to best.
+  enum {
+    NA, // Not an (IP) Address.
+    LO, // Loopback.
+    LL, // Link Local.
+    PR, // Private.
+    MC, // Multicast.
+    GL  // Global.
+  } spot_type = NA,
+    ip4_type = NA, ip6_type = NA;
+  addrinfo ai_hints;
+  addrinfo *ai_result;
+  IPAddrPair zret;
+
+  // Do the resolution
+  ink_zero(ai_hints);
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_flags  = AI_ADDRCONFIG;
+
+  if (0 == getaddrinfo(name.data(), nullptr, &ai_hints, &ai_result)) { // Walk the returned addresses and pick the "best".
+    for (addrinfo *ai_spot = ai_result; ai_spot; ai_spot = ai_spot->ai_next) {
+      swoc::IPAddr addr(ai_spot->ai_addr);
+      if (!addr.is_valid()) {
+        spot_type = NA;
+      } else if (addr.is_loopback()) {
+        spot_type = LO;
+      } else if (addr.is_link_local()) {
+        spot_type = LL;
+      } else if (addr.is_private()) {
+        spot_type = PR;
+      } else if (addr.is_multicast()) {
+        spot_type = MC;
+      } else {
+        spot_type = GL;
+      }
+
+      if (spot_type == NA) {
+        continue; // Next!
+      }
+
+      if (addr.is_ip4()) {
+        if (spot_type > ip4_type) {
+          zret     = addr.ip4();
+          ip4_type = spot_type;
+        }
+      } else if (addr.is_ip6()) {
+        if (spot_type > ip6_type) {
+          zret     = addr.ip6();
+          ip6_type = spot_type;
+        }
+      }
+    }
+
+    freeaddrinfo(ai_result); // free *after* the copy.
+  }
+
+  return zret;
+}
+
+IPSrvPair
+getbestsrvinfo(swoc::TextView src)
+{
+  swoc::TextView addr_text;
+  swoc::TextView port_text;
+  if (swoc::IPEndpoint::tokenize(src, &addr_text, &port_text)) {
+    in_port_t port = swoc::svtoi(port_text);
+    return IPSrvPair{ts::getbestaddrinfo(addr_text), port};
+  }
+  return {};
+}
+
+} // namespace ts