diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index 1c9826e4cee..bde4eb3d011 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 00000000000..2248b985dc8 --- /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 + +#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 _ip4; + std::optional _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 _ip4; + std::optional _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 _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 d018e49568c..66dffc2cc3e 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 00000000000..01e8ae8dccb --- /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 +#include +#include + +#include + +#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(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