This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch dev-1-0-14 in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git
commit 4533c9af7fb1a62247a5ec4860218d2db37fd098 Author: Alan M. Carroll <[email protected]> AuthorDate: Sun Mar 1 20:26:12 2020 -0600 First pass at network extraction - IPv4, IPv6 passing initial tests, need to do generics now. --- swoc++/include/swoc/DiscreteRange.h | 29 +- swoc++/include/swoc/Errata.h | 24 +- swoc++/include/swoc/swoc_ip.h | 732 ++++++++++++++++++++++++++++++++---- swoc++/src/swoc_ip.cc | 210 ++++++++++- unit_tests/test_ip.cc | 27 ++ 5 files changed, 913 insertions(+), 109 deletions(-) diff --git a/swoc++/include/swoc/DiscreteRange.h b/swoc++/include/swoc/DiscreteRange.h index 2886a4b..c76f235 100644 --- a/swoc++/include/swoc/DiscreteRange.h +++ b/swoc++/include/swoc/DiscreteRange.h @@ -107,14 +107,12 @@ protected: T _max; ///< the maximum value in the interval public: - using metric_type = T; - using Relation = DiscreteRangeRelation; - using EdgeRelation = DiscreteRangeEdgeRelation; - -// static constexpr self_type ALL{detail::minimum<metric_type>(), detail::maximum<metric_type>()}; + using metric_type = T; ///< Export metric type. + using Relation = DiscreteRangeRelation; ///< Import type for convenience. + using EdgeRelation = DiscreteRangeEdgeRelation; ///< Import type for convenience. /** Default constructor. - An empty range is constructed. + An invalid (empty) range is constructed. */ constexpr DiscreteRange() : _min(detail::maximum<T>()), _max(detail::minimum<T>()) {} @@ -134,6 +132,13 @@ public: ~DiscreteRange() = default; + /** Check if there are no values in the range. + * + * @return @c true if the range is empty (contains no values), @c false if it contains at least + * one value. + */ + bool empty() const; + self_type &assign(metric_type const &min, metric_type const &max); /// Set the interval to be a singleton. @@ -159,8 +164,13 @@ public: */ metric_type const &max() const; - bool contains(metric_type const& n) { - return _min <= n && n <= _max; + /** Check if a value is in @a this range. + * + * @param m Metric value to check. + * @return @c true if @a m is in the range, @c false if not. + */ + bool contains(metric_type const& m) { + return _min <= m && m <= _max; } /** Logical intersection test for two intervals. @@ -249,9 +259,6 @@ public: //! Check if the interval is exactly one element. bool is_singleton() const; - //! Check if the interval is empty. - bool empty() const; - /** Test for empty, operator form. @return @c true if the interval is empty, @c false otherwise. */ diff --git a/swoc++/include/swoc/Errata.h b/swoc++/include/swoc/Errata.h index 5cc3db5..f352e8b 100644 --- a/swoc++/include/swoc/Errata.h +++ b/swoc++/include/swoc/Errata.h @@ -905,31 +905,31 @@ namespace swoc template <size_t IDX, typename R> typename std::tuple_element<IDX, swoc::Rv<R>>::type & get(swoc::Rv<R> &&rv) { - if constexpr (IDX == 0) - { + if constexpr (IDX == 0) { return rv.result(); - } else if constexpr (IDX == 1) - { return rv.errata(); } + } else if constexpr (IDX == 1) { + return rv.errata(); + } } template <size_t IDX, typename R> typename std::tuple_element<IDX, swoc::Rv<R>>::type & get(swoc::Rv<R> &rv) { - if constexpr (IDX == 0) - { + if constexpr (IDX == 0) { return rv.result(); - } else if constexpr (IDX == 1) - { return rv.errata(); } + } else if constexpr (IDX == 1) { + return rv.errata(); + } } template <size_t IDX, typename R> typename std::tuple_element<IDX, swoc::Rv<R>>::type const & get(swoc::Rv<R> const &rv) { - if constexpr (IDX == 0) - { + if constexpr (IDX == 0) { return rv.result(); - } else if constexpr (IDX == 1) - { return rv.errata(); } + } else if constexpr (IDX == 1) { + return rv.errata(); + } } } // namespace swoc diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h index 0ee3f9b..736a6e6 100644 --- a/swoc++/include/swoc/swoc_ip.h +++ b/swoc++/include/swoc/swoc_ip.h @@ -1,4 +1,5 @@ #pragma once +#pragma once // SPDX-License-Identifier: Apache-2.0 /** @file IP address and network related classes. @@ -11,6 +12,7 @@ #include <swoc/TextView.h> #include <swoc/DiscreteRange.h> #include <swoc/RBTree.h> +#include <values.h> namespace swoc { @@ -21,6 +23,8 @@ class IPMask; class IP4Range; class IP6Range; class IPRange; +class IP4Net; +class IP6Net; using ::std::string_view; @@ -156,10 +160,11 @@ class IP4Addr { friend class IP4Range; public: static constexpr size_t SIZE = sizeof(in_addr_t); ///< Size of IPv4 address in bytes. - static const self_type MIN; - static const self_type MAX; + static constexpr size_t WIDTH = BITSPERBYTE * SIZE; ///< # of bits in an address. + static const self_type MIN; ///< Minimum value. + static const self_type MAX; ///< Maximum value. - constexpr IP4Addr() = default; ///< Default constructor - invalid result. + constexpr IP4Addr() = default; ///< Default constructor - minimum address. /// Construct using IPv4 @a addr (in host order). /// @note Host order seems odd, but all of the standard network macro values such as @c INADDR_LOOPBACK @@ -227,6 +232,38 @@ public: /// Test for loopback bool is_loopback() const { return (*this)[0] == IN_LOOPBACKNET; } + /** Left shift. + * + * @param n Number of bits to shift left. + * @return @a this. + */ + self_type & operator <<= (unsigned n); + + /** Right shift. + * + * @param n Number of bits to shift right. + * @return @a this. + */ + self_type & operator >>= (unsigned n); + + /** Bitwise AND. + * + * @param that Source address. + * @return @a this. + * + * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that. + */ + self_type & operator &= (self_type const& that); + + /** Bitwise OR. + * + * @param that Source address. + * @return @a this. + * + * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that. + */ + self_type & operator |= (self_type const& that); + /** Convert between network and host order. * * @param src Input address. @@ -255,18 +292,41 @@ protected: class IP6Addr { using self_type = IP6Addr; ///< Self reference type. friend class IP6Range; + friend class IPMask; public: - using QUAD = uint16_t; ///< Size of one segment of an IPv6 address. - static constexpr size_t SIZE = 16; ///< Size of address in bytes. - static constexpr size_t WORD_SIZE = sizeof(uint64_t); ///< Size of words used to store address. - static constexpr size_t N_QUADS = SIZE / sizeof(QUAD); ///< # of quads in an IPv6 address. + static constexpr size_t WIDTH = 128; ///< Number of bits in the address. + static constexpr size_t SIZE = WIDTH / BITSPERBYTE; ///< Size of address in bytes. + + using quad_type = uint16_t; ///< Size of one segment of an IPv6 address. + static constexpr size_t N_QUADS = SIZE / sizeof(quad_type); ///< # of quads in an IPv6 address. /// Direct access type for the address. /// Equivalent to the data type for data member @c s6_addr in @c in6_addr. using raw_type = std::array<unsigned char, SIZE>; + /// Direct access type for the address by quads (16 bits). /// This corresponds to the elements of the text format of the address. - using quad_type = std::array<unsigned short, N_QUADS>; + using quad_store_type = std::array<quad_type, N_QUADS>; + + /// Number of bits per quad. + static constexpr size_t QUAD_WIDTH = BITSPERBYTE * sizeof(quad_type); + + /// A bit mask of all 1 bits the size of a quad. + static constexpr quad_type QUAD_MASK = ~quad_type{0}; + + /// Type used as a "word", the natural working unit of the address. + using word_type = uint64_t; + + static constexpr size_t WORD_SIZE = sizeof(word_type); + + /// Number of bits per word. + static constexpr size_t WORD_WIDTH = BITSPERBYTE * WORD_SIZE; + + /// Number of words used for basic address storage. + static constexpr size_t N_STORE = SIZE / sizeof(word_type); + + /// Type used to store the address. + using word_store_type = std::array<word_type, N_STORE>; /// Minimum value of an address. static const self_type MIN; @@ -274,6 +334,7 @@ public: static const self_type MAX; IP6Addr() = default; ///< Default constructor - 0 address. + IP6Addr(self_type const& that) = default; /// Construct using IPv6 @a addr. explicit IP6Addr(in6_addr const & addr); @@ -288,6 +349,38 @@ public: /// Construct from generic @a addr. IP6Addr(IPAddr const& addr); + /** Left shift. + * + * @param n Number of bits to shift left. + * @return @a this. + */ + self_type & operator <<= (unsigned n); + + /** Right shift. + * + * @param n Number of bits to shift right. + * @return @a this. + */ + self_type & operator >>= (unsigned n); + + /** Bitwise AND. + * + * @param that Source address. + * @return @a this. + * + * The bits in @a this are set to the bitwise AND of the corresponding bits in @a this and @a that. + */ + self_type & operator &= (self_type const& that); + + /** Bitwise OR. + * + * @param that Source address. + * @return @a this. + * + * The bits in @a this are set to the bitwise OR of the corresponding bits in @a this and @a that. + */ + self_type & operator |= (self_type const& that); + /// Increment address. self_type &operator++(); @@ -331,13 +424,15 @@ public: bool is_multicast() const { return IN6_IS_ADDR_MULTICAST(_addr._raw.data()); } self_type & clear() { - _addr._u64[0] = _addr._u64[1] = 0; + _addr._store[0] = _addr._store[1] = 0; return *this; } + self_type & operator &= (IPMask const& mask); + self_type & operator |= (IPMask const& mask); + static void reorder(in6_addr & dst, raw_type const & src); static void reorder(raw_type & dst, in6_addr const& src); - static void reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]); protected: friend bool operator==(self_type const &, self_type const &); @@ -347,21 +442,29 @@ protected: /// Type for digging around inside the address, with the various forms of access. union { - uint64_t _u64[2] = {0, 0}; ///< 0 is MSW, 1 is LSW. - quad_type _quad; ///< By quad. + word_store_type _store = {0 }; ///< 0 is MSW, 1 is LSW. + quad_store_type _quad; ///< By quad. raw_type _raw; ///< By byte. } _addr; + static constexpr unsigned LSW = 1; ///< Least significant word index. + static constexpr unsigned MSW = 0; ///< Most significant word index. + /// Index of quads in @a _addr._quad. /// This converts from the position in the text format to the quads in the binary format. static constexpr std::array<unsigned, N_QUADS> QUAD_IDX = { 3,2,1,0,7,6,5,4 }; + static void reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]); + /** Construct from two 64 bit values. * * @param msw The most significant 64 bits, host order. * @param lsw The least significant 64 bits, host order. */ - IP6Addr(uint64_t msw, uint64_t lsw) : _addr{msw, lsw} {} + IP6Addr(word_store_type::value_type msw, word_store_type::value_type lsw) : _addr{msw, lsw} {} + + friend IP6Addr operator & (IP6Addr const& addr, IPMask const& mask); + friend IP6Addr operator | (IP6Addr const& addr, IPMask const& mask); }; /** Storage for an IP address. @@ -412,9 +515,6 @@ public: self_type &operator&=(IPMask const& mask); self_type &operator|=(IPMask const& mask); - /// Write to @c sockaddr. - sockaddr *fill(sockaddr *sa, in_port_t port = 0) const; - /** Parse a string and load the result in @a this. * * @param text Text to parse. @@ -441,8 +541,10 @@ public: in6_addr network_ip6() const; explicit operator IP4Addr const&() const { return _addr._ip4; } + explicit operator IP4Addr &() { return _addr._ip4; } explicit operator IP6Addr const&() const { return _addr._ip6; } + explicit operator IP6Addr &() { return _addr._ip6; } /// Test for validity. bool is_valid() const; @@ -488,34 +590,75 @@ protected: */ class IPMask { using self_type = IPMask; ///< Self reference type. - using raw_type = uint8_t; ///< Storage for mask width. + friend class IP4Addr; + friend class IP6Addr; public: + using raw_type = uint8_t; ///< Storage for mask width. + IPMask() = default; - IPMask(raw_type count, sa_family_t family = AF_INET); - IPMask(string_view text); + explicit IPMask(raw_type count); + + /// @return @c true if the mask is valid, @c false if not. + bool is_valid() const; bool load(string_view const& text); - /** Get the CIDR mask wide enough to cover this address. - * @param addr Input address. - * @return Effectively the reverse index of the least significant bit set to 1. + /** Copmute a mask for the network at @a addr. + * @param addr Lower bound of network. + * @return The width of the largest network starting at @a addr. + */ + static self_type mask_for(IPAddr const& addr); + + /** Copmute a mask for the network at @a addr. + * @param addr Lower bound of network. + * @return A mask with the width of the largest network starting at @a addr. */ - int cidr_of(IPAddr addr); + static self_type mask_for(IP4Addr const& addr); + + /** Copmute a mask for the network at @a addr. + * @param addr Lower bound of network. + * @return A mask with the width of the largest network starting at @a addr. + */ + static self_type mask_for(IP6Addr const& addr); /// The width of the mask. raw_type width() const; - /// Family type. - sa_family_t family() const { return _family; } + self_type & operator<<= (raw_type n) { + _cidr -= n; + return *this; + } + + self_type & operator>>= (raw_type n) { + _cidr += n; + return *this; + } + + /** The mask as an IPv4 address. + * + * @return An IPv4 address that is the mask. + * + * If the mask is wider than an IPv4 address, the maximum mask is returned. + */ + IP4Addr as_ip4() const; + + /** The mask as an IPv6 address. + * + * @return An IPv6 address that is the mask. + * + * If the mask is wider than an IPv6 address, the maximum mask is returned. + */ + IP6Addr as_ip6() const; + +protected: + /// Marker value for an invalid mask. + static constexpr auto INVALID = std::numeric_limits<raw_type>::max(); - /// Write the mask as an address to @a addr. - /// @return The filled address. - IPAddr &fill(IPAddr &addr); + raw_type _cidr = INVALID; ///< Mask width in bits. -private: - raw_type _mask{0}; - sa_family_t _family{AF_UNSPEC}; + /// Compute a partial IPv6 mask, sized for the basic storage type. + static raw_type mask_for_quad(IP6Addr::quad_type q); }; @@ -568,7 +711,80 @@ public: */ bool load(string_view text); + class NetSource; + + /** Generate a list of networks covering @a this range. + * + * @return A network generator. + * + * The returned object can be used as an iterator, or as a container to iterating over + * the unique minimal set of networks that cover @a this range. + * + * @code + * void (IP4Range const& range) { + * for ( auto const& net : range ) { + * net.addr(); // network address. + * net.mask(); // network mask; + * } + * } + * @endcode + */ + NetSource networks() const; +}; + +/** Network generator class. + * This generates networks from a range and acts as both a forward iterator and a container. + */ +class IP4Range::NetSource { + using self_type = NetSource; ///< Self reference type. +public: + using range_type = IP4Range; ///< Import base range type. + + /// Construct from @a range. + explicit NetSource(range_type const& range); + /// Copy constructor. + NetSource(self_type const& that) = default; + + /// This class acts as a container and an iterator. + using iterator = self_type; + /// All iteration is constant so no distinction between iterators. + using const_iterator = iterator; + + iterator begin() const; ///< First network. + iterator end() const; ///< Past last network. + + /// @return The current network. + IP4Net operator * () const; + /// Access @a this as if it were an @c IP4Net. + self_type * operator -> (); + + /// Iterator support. + /// @areturn The current network address. + IP4Addr const& addr() const; + /// Iterator support. + /// @return The current network mask. + IPMask mask() const; + + /// Move to next network. + self_type & operator++(); + /// Move to next network. + self_type operator++(int); + + /// Equality. + bool operator == (self_type const& that); + + /// Inequality. + bool operator != (self_type const& that); + protected: + IP4Range _range; ///< Remaining range. + /// Mask for current network. + IP4Addr _mask{~static_cast<in_addr_t>(0)}; + IPMask::raw_type _cidr = IP4Addr::WIDTH; ///< Current CIDR value. + + void search_wider(); + void search_narrower(); + bool is_valid(IP4Addr mask); }; class IP6Range : public DiscreteRange<IP6Addr> { @@ -582,6 +798,16 @@ public: /// @internal Why do I have to do this, even though the super type constructors are inherited? IP6Range(super_type const& r) : super_type(r) {} + /** Construct range from @a text. + * + * @param text Range text. + * @see IP4Range::load + * + * This results in a zero address if @a text is not a valid string. If this should be checked, + * use @c load. + */ + IP6Range(string_view const& text); + /** Set @a this range. * * @param addr Minimum address. @@ -601,6 +827,78 @@ public: */ bool load(string_view text); + class NetSource; + + /** Generate a list of networks covering @a this range. + * + * @return A network generator. + * + * The returned object can be used as an iterator, or as a container to iterating over + * the unique minimal set of networks that cover @a this range. + * + * @code + * void (IP4Range const& range) { + * for ( auto const& net : range ) { + * net.addr(); // network address. + * net.mask(); // network mask; + * } + * } + * @endcode + */ + NetSource networks() const; +}; + +/** Network generator class. + * This generates networks from a range and acts as both a forward iterator and a container. + */ +class IP6Range::NetSource { + using self_type = NetSource; ///< Self reference type. +public: + using range_type = IP6Range; ///< Import base range type. + + /// Construct from @a range. + explicit NetSource(range_type const& range); + /// Copy constructor. + NetSource(self_type const& that) = default; + + /// This class acts as a container and an iterator. + using iterator = self_type; + /// All iteration is constant so no distinction between iterators. + using const_iterator = iterator; + + iterator begin() const; ///< First network. + iterator end() const; ///< Past last network. + + /// @return The current network. + IP6Net operator * () const; + /// Access @a this as if it were an @c IP6Net. + self_type * operator -> (); + + /// Iterator support. + /// @areturn The current network address. + IP6Addr const& addr() const; + /// Iterator support. + /// @return The current network mask. + IPMask mask() const; + + /// Move to next network. + self_type & operator++(); + /// Move to next network. + self_type operator++(int); + + /// Equality. + bool operator == (self_type const& that); + + /// Inequality. + bool operator != (self_type const& that); + +protected: + IP6Range _range; ///< Remaining range. + IPMask _mask { IP6Addr::WIDTH } ; ///< Current CIDR value. + + void search_wider(); + void search_narrower(); + bool is_valid(IPMask const& mask); }; class IPRange { @@ -668,14 +966,91 @@ protected: sa_family_t _family { AF_UNSPEC }; }; +/// An IPv4 network. +class IP4Net { + using self_type = IP4Net; ///< Self reference type. +public: + IP4Net() = default; ///< Construct invalid network. + IP4Net(self_type const& that) = default; ///< Copy constructor. + + /** Construct from @a addr and @a mask. + * + * @param addr An address in the network. + * @param mask The mask for the network. + * + * The network is based on the mask, and the resulting network address is chosen such that the + * network will contain @a addr. For a given @a addr and @a mask there is only one network + * that satisifies these criteria. + */ + IP4Net(IP4Addr addr, IPMask mask); + + /// @return @c true if the network is valid, @c false if not. + bool is_valid() const; + + /// @return THh smallest address in the network. + IP4Addr lower_bound() const; + + /// @return The largest address in the network. + IP4Addr upper_bound() const; + + /// @return The mask for the network. + IPMask const& mask() const; + + /// @return A range that exactly covers the network. + IP4Range as_range() const; + +protected: + IP4Addr _addr; ///< Network address (also lower_bound). + IPMask _mask; ///< Network mask. +}; + +class IP6Net { + using self_type = IP6Net; ///< Self reference type. +public: + IP6Net() = default; ///< Construct invalid network. + IP6Net(self_type const& that) = default; ///< Copy constructor. + + /** Construct from @a addr and @a mask. + * + * @param addr An address in the network. + * @param mask The mask for the network. + * + * The network is based on the mask, and the resulting network address is chosen such that the + * network will contain @a addr. For a given @a addr and @a mask there is only one network + * that satisifies these criteria. + */ + IP6Net(IP6Addr addr, IPMask mask); + + /// @return @c true if the network is valid, @c false if not. + bool is_valid() const; + + /// @return THh smallest address in the network. + IP6Addr lower_bound() const; + + /// @return The largest address in the network. + IP6Addr upper_bound() const; + + /// @return The mask for the network. + IPMask const& mask() const; + + /// @return A range that exactly covers the network. + IP6Range as_range() const; + +protected: + IP6Addr _addr; ///< Network address (also lower_bound). + IPMask _mask; ///< Network mask. +}; + /** Representation of an IP address network. * */ -class IpNet { - using self_type = IpNet; ///< Self reference type. +class IPNet { + using self_type = IPNet; ///< Self reference type. public: - IpNet(); - IpNet(const IPAddr &addr, const IPMask &mask); + static constexpr char SEPARATOR = '/'; // the character used between the address and mask + + IPNet() = default; + IPNet(const IPAddr &addr, const IPMask &mask); operator IPAddr const &() const; operator IPMask const &() const; @@ -687,6 +1062,8 @@ public: IPAddr lower_bound() const; IPAddr upper_bound() const; + IPRange as_range() const; + bool contains(IPAddr const &addr) const; // computes this is strict subset of other @@ -697,16 +1074,6 @@ public: self_type &assign(IPAddr const &addr, IPMask const &mask); - static char const SEPARATOR; // the character used between the address and mask - - operator ::std::string() const; // implicit - ::std::string ntoa() const; // explicit - // the address width is per octet, the mask width for the bit count - ::std::string ntoa(int addr_width, int mask_width) const; - - static ::std::string ntoa(IpNet const &net); // DEPRECATED - static IpNet aton(::std::string const &str); // DEPRECATED - use ctor - protected: IPAddr _addr; IPMask _mask; @@ -1391,6 +1758,26 @@ inline IP4Addr::IP4Addr(std::string_view const& text) { inline IP4Addr::IP4Addr(IPAddr const& addr) : _addr(addr._family == AF_INET ? addr._addr._ip4._addr : INADDR_ANY) {} +inline IP4Addr& IP4Addr::operator<<=(unsigned n) { + _addr <<= n; + return *this; +} + +inline IP4Addr& IP4Addr::operator>>=(unsigned n) { + _addr >>= n; + return *this; +} + +inline IP4Addr& IP4Addr::operator&=(self_type const& that) { + _addr &= that._addr; + return *this; +} + +inline IP4Addr& IP4Addr::operator|=(self_type const& that) { + _addr |= that._addr; + return *this; +} + inline IP4Addr & IP4Addr::operator++() { ++_addr; @@ -1442,16 +1829,12 @@ inline bool operator >= (IP4Addr const& lhs, IP4Addr const& rhs) { } inline IP4Addr & IP4Addr::operator&=(IPMask const& mask) { - auto n = std::min<unsigned>(32, mask.width()); - in_addr_t bits = htonl(INADDR_BROADCAST << (32 - n)); - _addr &= bits; + _addr &= mask.as_ip4()._addr; return *this; } inline IP4Addr & IP4Addr::operator|=(IPMask const& mask) { - auto n = std::min<unsigned>(32, mask.width()); - in_addr_t bits = htonl(INADDR_BROADCAST << (32 - n)); - _addr |= bits; + _addr |= ~(mask.as_ip4()._addr); return *this; } @@ -1497,38 +1880,38 @@ inline auto IP6Addr::operator = (sockaddr_in6 const* addr) -> self_type & { inline IP6Addr & IP6Addr::operator++() { - if (++(_addr._u64[1]) == 0) { - ++(_addr._u64[0]); + if (++(_addr._store[1]) == 0) { + ++(_addr._store[0]); } return *this; } inline IP6Addr & IP6Addr::operator--() { - if (--(_addr._u64[1]) == ~static_cast<uint64_t >(0)) { - --(_addr._u64[0]); + if (--(_addr._store[1]) == ~static_cast<uint64_t >(0)) { + --(_addr._store[0]); } return *this; } inline void IP6Addr::reorder(unsigned char dst[WORD_SIZE], unsigned char const src[WORD_SIZE]) { - for ( size_t idx = 0 ; idx < WORD_SIZE ; ++idx ) { + for (size_t idx = 0 ; idx < WORD_SIZE ; ++idx ) { dst[idx] = src[WORD_SIZE - (idx + 1)]; } } inline bool operator == (IP6Addr const& lhs, IP6Addr const& rhs) { - return lhs._addr._u64[0] == rhs._addr._u64[0] && - lhs._addr._u64[1] == rhs._addr._u64[1]; + return lhs._addr._store[0] == rhs._addr._store[0] && + lhs._addr._store[1] == rhs._addr._store[1]; } inline bool operator != (IP6Addr const& lhs, IP6Addr const& rhs) { - return lhs._addr._u64[0] != rhs._addr._u64[0] || - lhs._addr._u64[1] != rhs._addr._u64[1]; + return lhs._addr._store[0] != rhs._addr._store[0] || + lhs._addr._store[1] != rhs._addr._store[1]; } inline bool operator < (IP6Addr const& lhs, IP6Addr const& rhs) { - return lhs._addr._u64[0] < rhs._addr._u64[0] || (lhs._addr._u64[0] == rhs._addr._u64[0] && lhs._addr._u64[1] < rhs._addr._u64[1]); + return lhs._addr._store[0] < rhs._addr._store[0] || (lhs._addr._store[0] == rhs._addr._store[0] && lhs._addr._store[1] < rhs._addr._store[1]); } inline bool operator > (IP6Addr const& lhs, IP6Addr const& rhs) { @@ -1536,13 +1919,33 @@ inline bool operator > (IP6Addr const& lhs, IP6Addr const& rhs) { } inline bool operator <= (IP6Addr const& lhs, IP6Addr const& rhs) { - return lhs._addr._u64[0] < rhs._addr._u64[0] || (lhs._addr._u64[0] == rhs._addr._u64[0] && lhs._addr._u64[1] <= rhs._addr._u64[1]); + return lhs._addr._store[0] < rhs._addr._store[0] || (lhs._addr._store[0] == rhs._addr._store[0] && lhs._addr._store[1] <= rhs._addr._store[1]); } inline bool operator >= (IP6Addr const& lhs, IP6Addr const& rhs) { return rhs <= lhs; } +inline IP6Addr& IP6Addr::operator &= (IPMask const& mask) { + if (mask._cidr < WORD_WIDTH) { + _addr._store[MSW] &= (~word_type{0} << (WORD_WIDTH - mask._cidr)); + _addr._store[LSW] = 0; + } else if (mask._cidr < WIDTH){ + _addr._store[LSW] &= (~word_type{0} << (2 * WORD_WIDTH - mask._cidr)); + } + return *this; +} + +inline IP6Addr& IP6Addr::operator |= (IPMask const& mask) { + if (mask._cidr < WORD_WIDTH) { + _addr._store[MSW] |= (~word_type{0} >> mask._cidr); + _addr._store[LSW] = ~word_type{0}; + } else if (mask._cidr < WIDTH) { + _addr._store[LSW] |= (~word_type{0} >> (mask._cidr - WORD_WIDTH)); + } + return *this; +} + // Disambiguating comparisons. inline bool operator == (IPAddr const& lhs, IP4Addr const& rhs) { @@ -1583,6 +1986,18 @@ inline IP4Range::IP4Range(string_view const& text) { this->load(text); } +inline auto IP4Range::networks() const -> NetSource { + return { NetSource{*this} }; +} + +inline IP6Range::IP6Range(string_view const& text) { + this->load(text); +} + +inline auto IP6Range::networks() const -> NetSource { + return { NetSource{*this} }; +} + inline IPRange::IPRange(IP4Range const& range) : _family(AF_INET) { _range._ip4 = range; } @@ -1597,13 +2012,14 @@ inline IPRange::IPRange(string_view const& text) { inline bool IPRange::is(sa_family_t family) const { return family == _family; } -// +++ IpMask +++ +// +++ IPMask +++ -inline IPMask::IPMask(raw_type width, sa_family_t family) : _mask(width), _family(family) {} +inline IPMask::IPMask(raw_type width) : _cidr(width) {} -inline IPMask::raw_type -IPMask::width() const { - return _mask; +inline bool IPMask::is_valid() const { return _cidr < INVALID; } + +inline auto IPMask::width() const -> raw_type { + return _cidr; } inline bool @@ -1621,28 +2037,141 @@ operator<(IPMask const &lhs, IPMask const &rhs) { return lhs.width() < rhs.width(); } -inline IpNet::IpNet() {} +inline IP4Addr IPMask::as_ip4() const { + static constexpr auto MASK = ~in_addr_t{0}; + in_addr_t addr = MASK; + if (_cidr < IP4Addr::WIDTH) { + addr <<=IP4Addr::WIDTH - _cidr; + } + return IP4Addr{ addr }; +} + +// +++ mixed operators +++ + +inline IP4Addr operator & (IP4Addr const& addr, IPMask const& mask) { + return IP4Addr{addr} &= mask; +} + +inline IP4Addr operator | (IP4Addr const& addr, IPMask const& mask) { + return IP4Addr {addr} |= mask; +} + +inline IP6Addr operator & (IP6Addr const& addr, IPMask const& mask) { + return IP6Addr{addr} &= mask; +} + +inline IP6Addr operator | (IP6Addr const& addr, IPMask const& mask) { + return IP6Addr{addr} |= mask; +} + +inline IPAddr operator & (IPAddr const& addr, IPMask const& mask) { + return IPAddr{addr} &= mask; +} -inline IpNet::IpNet(IPAddr const &addr, IPMask const &mask) : _addr(addr), _mask(mask) {} +inline IPAddr operator | (IPAddr const& addr, IPMask const& mask) { + return IPAddr {addr} |= mask; +} -inline IpNet::operator IPAddr const &() const { +// +++ IPNet +++ + +inline IP4Net::IP4Net(swoc::IP4Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {} +inline IPMask const& IP4Net::mask() const { return _mask; } +inline bool IP4Net::is_valid() const { return _mask.is_valid(); } +inline IP4Addr IP4Net::lower_bound() const { return _addr; } +inline IP4Addr IP4Net::upper_bound() const { return _addr | _mask; } +inline IP4Range IP4Net::as_range() const { return { this->lower_bound(), this->upper_bound()}; } + +inline IP6Net::IP6Net(swoc::IP6Addr addr, swoc::IPMask mask) : _addr(addr & mask), _mask(mask) {} +inline IPMask const& IP6Net::mask() const { return _mask; } +inline bool IP6Net::is_valid() const { return _mask.is_valid(); } +inline IP6Addr IP6Net::lower_bound() const { return _addr; } +inline IP6Addr IP6Net::upper_bound() const { return _addr | _mask; } +inline IP6Range IP6Net::as_range() const { return { this->lower_bound(), this->upper_bound()}; } + +inline IPNet::IPNet(IPAddr const &addr, IPMask const &mask) : _addr(addr & mask), _mask(mask) {} + +inline IPNet::operator IPAddr const &() const { return _addr; } -inline IpNet::operator IPMask const &() const { +inline IPNet::operator IPMask const &() const { return _mask; } inline IPAddr const & -IpNet::addr() const { +IPNet::addr() const { return _addr; } inline IPMask const & -IpNet::mask() const { +IPNet::mask() const { return _mask; } +inline IPAddr IPNet::lower_bound() const { return _addr; } + +inline IPAddr IPNet::upper_bound() const { return _addr | _mask; } + +// +++ Range -> Network classes +++ + +inline bool IP4Range::NetSource::is_valid(swoc::IP4Addr mask) { + return ((mask._addr & _range._min._addr) == _range._min._addr) && + ((_range._min._addr | ~mask._addr) <= _range._max._addr); +} + +inline IP4Net IP4Range::NetSource::operator*() const { + return IP4Net{_range.min(), IPMask{_cidr}}; +} + +inline IP4Range::NetSource::iterator IP4Range::NetSource::begin() const { + return *this; +} + +inline IP4Range::NetSource::iterator IP4Range::NetSource::end() const { + return self_type{range_type{}}; +} + +inline IPMask IP4Range::NetSource::mask() const { return IPMask{ _cidr }; } + +inline auto IP4Range::NetSource::operator->() -> self_type * { return this; } + +inline IP4Addr const& IP4Range::NetSource::addr() const { return _range.min(); } + +inline bool IP4Range::NetSource::operator==(IP4Range::NetSource::self_type const& that) { + return ((_cidr == that._cidr) && (_range == that._range)) || (_range.empty() && that._range.empty()); +} + +inline bool IP4Range::NetSource::operator!=(IP4Range::NetSource::self_type const&that) { + return ! (*this == that); +} + +inline IP6Range::NetSource::iterator IP6Range::NetSource::begin() const { + return *this; +} + +inline IP6Range::NetSource::iterator IP6Range::NetSource::end() const { + return self_type{range_type{}}; +} + +inline IP6Net IP6Range::NetSource::operator*() const { + return IP6Net{_range.min(), _mask}; +} + +inline auto IP6Range::NetSource::operator->() -> self_type * { return this; } + +inline bool IP6Range::NetSource::is_valid(IPMask const& mask) { + return ((_range.min() & mask) == _range.min()) && + ((_range.min() | mask) <= _range.max()); +} + +inline bool IP6Range::NetSource::operator==(IP6Range::NetSource::self_type const& that) { + return ((_mask == that._mask) && (_range == that._range)) || (_range.empty() && that._range.empty()); +} + +inline bool IP6Range::NetSource::operator!=(IP6Range::NetSource::self_type const&that) { + return ! (*this == that); +} + // --- IPSpace template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(IPRange const &range, PAYLOAD const &payload) -> self_type & { @@ -1692,5 +2221,62 @@ auto IPSpace<PAYLOAD>::end() const -> const_iterator { return const_iterator(nc_this->_ip4.end(), nc_this->_ip6.end()); } +} // namespace swoc + +namespace std { + +template <> class tuple_size<swoc::IP4Net> : public std::integral_constant<size_t, 2> {}; + +template < size_t IDX > class tuple_element<IDX, swoc::IP4Net> { + static_assert("swoc::IP4Net tuple index out of range"); +}; + +template <> class tuple_element<0, swoc::IP4Net> { +public: + using type = swoc::IP4Addr; +}; + +template <> class tuple_element<1, swoc::IP4Net> { +public: + using type = swoc::IPMask; +}; + +template <> class tuple_size<swoc::IP6Net> : public std::integral_constant<size_t, 2> {}; + +template < size_t IDX > class tuple_element<IDX, swoc::IP6Net> { + static_assert("swoc::IP6Net tuple index out of range"); +}; + +template <> class tuple_element<0, swoc::IP6Net> { +public: + using type = swoc::IP6Addr; +}; + +template <> class tuple_element<1, swoc::IP6Net> { +public: + using type = swoc::IPMask; +}; + +} // namespace std + +namespace swoc { + +template < size_t IDX > typename std::tuple_element<IDX, IP4Net>::type +get(swoc::IP4Net const& net) { + if constexpr (IDX == 0) { + return net.lower_bound(); + } else if constexpr (IDX == 1) { + return net.mask(); + } +} + +template < size_t IDX > typename std::tuple_element<IDX, IP6Net>::type +get(swoc::IP6Net const& net) { + if constexpr (IDX == 0) { + return net.lower_bound(); + } else if constexpr (IDX == 1) { + return net.mask(); + } +} } // namespace swoc diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc index 7fd4264..283b557 100644 --- a/swoc++/src/swoc_ip.cc +++ b/swoc++/src/swoc_ip.cc @@ -190,6 +190,7 @@ IPEndpoint::parse(std::string_view const&str) { return false; } +#if 0 sockaddr * IPAddr::fill(sockaddr *sa, in_port_t port) const { switch (sa->sa_family = _family) { @@ -211,6 +212,7 @@ IPAddr::fill(sockaddr *sa, in_port_t port) const { } return sa; } +#endif socklen_t IPEndpoint::size() const { @@ -302,6 +304,46 @@ sockaddr_in *IP4Addr::fill(sockaddr_in *sa, in_port_t port) const { return sa; } +IP6Addr& IP6Addr::operator<<=(unsigned int n) { + static constexpr auto MASK = ~word_type{0}; + if (n < WORD_WIDTH) { + _addr._store[MSW] <<= n; + _addr._store[MSW] |= (_addr._store[LSW] >> (WORD_WIDTH - n)) & ~(MASK << n); + _addr._store[LSW] <<= n; + } else { + n -= WORD_WIDTH; + _addr._store[MSW] = _addr._store[LSW] << n; + _addr._store[LSW] = 0; + } + return *this; +} + +IP6Addr& IP6Addr::operator>>=(unsigned int n) { + static constexpr auto MASK = ~word_type{0}; + if (n < WORD_WIDTH) { + _addr._store[LSW] >>= n; + _addr._store[LSW] |= (_addr._store[MSW] & ~(MASK << n)) << (WORD_WIDTH - n); + _addr._store[MSW] >>= n; + } else { + n -= WORD_WIDTH; + _addr._store[LSW] = _addr._store[MSW] >> n; + _addr._store[MSW] = 0; + } + return *this; +} + +IP6Addr& IP6Addr::operator&=(self_type const& that) { + _addr._store[MSW] &= that._addr._store[MSW]; + _addr._store[LSW] &= that._addr._store[LSW]; + return *this; +} + +IP6Addr& IP6Addr::operator|=(self_type const& that) { + _addr._store[MSW] |= that._addr._store[MSW]; + _addr._store[LSW] |= that._addr._store[LSW]; + return *this; +} + bool IP6Addr::load(std::string_view const&str) { TextView src{str}; @@ -329,8 +371,8 @@ IP6Addr::load(std::string_view const&str) { this->clear(); return true; } else if (src.size() == 3 && src[2] == '1') { - _addr._u64[0] = 0; - _addr._u64[1] = 1; + _addr._store[0] = 0; + _addr._store[1] = 1; return true; } else { empty_idx = n; @@ -512,14 +554,72 @@ IPEndpoint::IPEndpoint(std::string_view const&text) { bool IPMask::load(string_view const&text) { TextView parsed; - _mask = swoc::svtou(text, &parsed); + _cidr = swoc::svtou(text, &parsed); if (parsed.size() != text.size()) { - _mask = 0; + _cidr = 0; return false; } return true; } +IPMask IPMask::mask_for(IPAddr const&addr) { + if (addr.is_ip4()) { + return self_type::mask_for(static_cast<IP4Addr const&>(addr)); + } else if (addr.is_ip6()) { + return self_type::mask_for(static_cast<IP6Addr const&>(addr)); + } + return {}; +} + +auto IPMask::mask_for_quad(IP6Addr::quad_type q) -> raw_type { + raw_type cidr = IP6Addr::QUAD_WIDTH; + if (q != 0) { + auto mask = IP6Addr::QUAD_MASK; + do { + mask <<= 1; + --cidr; + } while ((q | ~mask) == q); + ++cidr; // loop goes exactly 1 too far. + } + return cidr; +} + +IPMask IPMask::mask_for(IP4Addr const& addr) { + auto n = addr.host_order(); + raw_type cidr = 0; + if (auto q = (n & IP6Addr::QUAD_MASK) ; q != 0) { + cidr = IP6Addr::QUAD_WIDTH + self_type::mask_for_quad(q); + } else if (auto q = ((n >> IP6Addr::QUAD_WIDTH) & IP6Addr::QUAD_MASK) ; q != 0) { + cidr = self_type::mask_for_quad(q); + } + return self_type(cidr); +} + +IPMask IPMask::mask_for(IP6Addr const& addr) { + auto cidr = IP6Addr::WIDTH; + for ( unsigned idx = IP6Addr::N_QUADS ; idx > 0 ; ) { + auto q = addr._addr._quad[IP6Addr::QUAD_IDX[--idx]]; + cidr -= IP6Addr::QUAD_WIDTH; + if (q != 0) { + cidr += self_type::mask_for_quad(q); + break; + } + } + return self_type(cidr); +} + +IP6Addr IPMask::as_ip6() const { + static constexpr auto MASK = ~IP6Addr::word_type{0}; + if (_cidr <= IP6Addr::WORD_WIDTH) { + return { MASK << (IP6Addr::WORD_WIDTH - _cidr), 0}; + } else if (_cidr < 2 * IP6Addr::WORD_WIDTH) { + return {MASK, MASK << (2 * IP6Addr::WORD_WIDTH - _cidr)}; + } + return { MASK, MASK }; +} + +// +++ IP4Range +++ + IP4Range::IP4Range(swoc::IP4Addr const&addr, swoc::IPMask const&mask) { this->assign(addr, mask); } @@ -568,6 +668,50 @@ bool IP4Range::load(string_view text) { return false; } +IP4Range::NetSource::NetSource(IP4Range::NetSource::range_type const&range) : _range(range) { + if (!_range.empty()) { + this->search_wider(); + } +} + +auto IP4Range::NetSource::operator++() -> self_type & { + auto upper (IP4Addr{_range._min._addr | ~_mask._addr} ); + if (upper >= _range.max()) { + _range.clear(); + } else { + _range.assign_min(++upper); + // @a _range is not empty, because there's at least one address still not covered. + if (this->is_valid(_mask)) { + this->search_wider(); + } else { + this->search_narrower(); + } + } + return *this; +} + +void IP4Range::NetSource::search_wider() { + while (_cidr > 0) { + auto m = _mask; + m <<= 1; + if (this->is_valid(m)) { + _mask = m; + --_cidr; + } else { + break; + } + } +} + +void IP4Range::NetSource::search_narrower() { + while (! this->is_valid(_mask)) { + _mask._addr = (_mask._addr >>= 1) | (1<<(IP4Addr::WIDTH-1)); + ++_cidr; + } +} + +// +++ IP6Range +++ + IP6Range&IP6Range::assign(IP6Addr const&addr, IPMask const&mask) { static constexpr auto FULL_MASK{std::numeric_limits<uint64_t>::max()}; auto cidr = mask.width(); @@ -576,20 +720,20 @@ IP6Range&IP6Range::assign(IP6Addr const&addr, IPMask const&mask) { _max = metric_type::MAX; } else if (cidr < 64) { // only upper bytes affected, lower bytes are forced. auto bits = FULL_MASK << (64 - cidr); - _min._addr._u64[0] = addr._addr._u64[0] & bits; - _min._addr._u64[1] = 0; - _max._addr._u64[0] = addr._addr._u64[0] | ~bits; - _max._addr._u64[1] = FULL_MASK; + _min._addr._store[0] = addr._addr._store[0] & bits; + _min._addr._store[1] = 0; + _max._addr._store[0] = addr._addr._store[0] | ~bits; + _max._addr._store[1] = FULL_MASK; } else if (cidr == 64) { - _min._addr._u64[0] = _max._addr._u64[0] = addr._addr._u64[0]; - _min._addr._u64[1] = 0; - _max._addr._u64[1] = FULL_MASK; + _min._addr._store[0] = _max._addr._store[0] = addr._addr._store[0]; + _min._addr._store[1] = 0; + _max._addr._store[1] = FULL_MASK; } else if (cidr <= 128) { // _min bytes changed, _max bytes unaffected. _min = _max = addr; if (cidr < 128) { auto bits = FULL_MASK << (128 - cidr); - _min._addr._u64[1] &= bits; - _max._addr._u64[1] |= ~bits; + _min._addr._store[1] &= bits; + _max._addr._store[1] |= ~bits; } } return *this; @@ -668,4 +812,44 @@ bool IPRange::empty() const { return true; } +IP6Range::NetSource::NetSource(IP6Range::NetSource::range_type const&range) : _range(range) { + if (!_range.empty()) { + this->search_wider(); + } +} + +auto IP6Range::NetSource::operator++() -> self_type & { + auto upper = _range.min() | _mask; + if (upper >= _range.max()) { + _range.clear(); + } else { + _range.assign_min(++upper); + // @a _range is not empty, because there's at least one address still not covered. + if (this->is_valid(_mask)) { + this->search_wider(); + } else { + this->search_narrower(); + } + } + return *this; +} + +void IP6Range::NetSource::search_wider() { + while (_mask.width() > 0) { + auto m = _mask; + m <<= 1; + if (this->is_valid(m)) { + _mask = m; + } else { + break; + } + } +} + +void IP6Range::NetSource::search_narrower() { + while (! this->is_valid(_mask)) { + _mask >>= 1; + } +} + } // namespace swoc diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc index 9d97cc2..711282b 100644 --- a/unit_tests/test_ip.cc +++ b/unit_tests/test_ip.cc @@ -269,6 +269,33 @@ TEST_CASE("IP Formatting", "[libswoc][ip][bwformat]") { REQUIRE(w.view() == " 0: 0: 0: 0: 0: 0: 0: 1"); } +TEST_CASE("IP ranges and networks", "[libswoc][ip][net][range]") { + swoc::IP4Range r_0; + swoc::IP4Range r_1{"1.1.1.0-1.1.1.9"}; + swoc::IP4Range r_2{"1.1.2.0-1.1.2.97"}; + swoc::IP4Range r_3{"1.1.0.0-1.2.0.0"}; + swoc::IP4Range r_4{"10.33.45.19-10.33.45.76"}; + swoc::IP6Range r_5{"2001:1f2d:c587:24c3:9128:3349:3cee:143-ffee:1f2d:c587:24c3:9128:3349:3cFF:FFFF"_tv}; + + CHECK(true == r_0.empty()); + CHECK(false == r_1.empty()); + + swoc::IPMask mask{127}; + CHECK(r_5.min() == (r_5.min() | swoc::IPMask(128))); + CHECK(r_5.min() == (r_5.min() | mask)); + CHECK(r_5.min() != (r_5.min() & mask)); + + swoc::IP6Addr a_1{"2001:1f2d:c587:24c4::"}; + CHECK(a_1 == (a_1 & swoc::IPMask{62})); + + for ( auto const& [ addr, mask ] : r_4.networks()) { + std::cout << W().print("{}/{}\n", addr, mask.width()); + } + for ( auto const& [ addr, mask ] : r_5.networks()) { + std::cout << W().print("{}/{}\n", addr, mask.width()); + } +} + TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") { using int_space = swoc::IPSpace<unsigned>; int_space space;
