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

bneradt pushed a commit to branch dev-1-1-0
in repository https://gitbox.apache.org/repos/asf/trafficserver-libswoc.git

commit 416f1e9a26de49b02d9a882668572792f470ba69
Author: Alan M. Carroll <[email protected]>
AuthorDate: Sun Mar 1 20:26:12 2020 -0600

    Network extraction support.
---
 swoc++/include/swoc/DiscreteRange.h |   29 +-
 swoc++/include/swoc/Errata.h        |   24 +-
 swoc++/include/swoc/MemSpan.h       |    2 +-
 swoc++/include/swoc/TextView.h      |   23 +-
 swoc++/include/swoc/swoc_ip.h       | 1744 ++++++++++++++++++++++++++++-------
 swoc++/src/bw_ip_format.cc          |    6 +-
 swoc++/src/swoc_ip.cc               |  328 ++++++-
 swoc++/swoc++-headers.part          |    6 +
 swoc++/swoc++-shared.part           |    2 +
 swoc++/swoc++-static.part           |    2 +
 swoc++/swoc++.part                  |   15 +-
 unit_tests/CMakeLists.txt           |    2 +-
 unit_tests/ex_ipspace_properties.cc |    3 +-
 unit_tests/test_ip.cc               |  339 ++++++-
 14 files changed, 2091 insertions(+), 434 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/MemSpan.h b/swoc++/include/swoc/MemSpan.h
index 81862c5..f341311 100644
--- a/swoc++/include/swoc/MemSpan.h
+++ b/swoc++/include/swoc/MemSpan.h
@@ -749,7 +749,7 @@ MemSpan<T>::remove_suffix(size_t count) {
 template <typename T>
 constexpr MemSpan<T>
 MemSpan<T>::subspan(size_t offset, size_t count) const {
-  return offset <= _count ? self_type{this->data() + offset, std::min(count, 
_count - offset)} : self_type{};
+  return offset < _count ? self_type{this->data() + offset, std::min(count, 
_count - offset)} : self_type{};
 }
 
 template <typename T>
diff --git a/swoc++/include/swoc/TextView.h b/swoc++/include/swoc/TextView.h
index 49d0909..db20a21 100644
--- a/swoc++/include/swoc/TextView.h
+++ b/swoc++/include/swoc/TextView.h
@@ -174,11 +174,25 @@ public:
   /// Assign from a @c std::string.
   self_type &operator=(const std::string &s);
 
+  /** Assign a view of the @a c_str
+   *
+   * @param c_str Pointer to C string.
+   * @return @a this
+   *
+   * @note @c c_str must be a null terminated string. The null byte is not 
included in the view.
+   */
+  self_type& assign(char const* c_str);;
+
   /// Explicitly set the start @a ptr and size @a n of the view.
   self_type &assign(char const *ptr, size_t n);
 
-  /// Explicitly set the view to the half open range [ @a first , @a last )
-  self_type &assign(char const *first, char const *lsat);
+  /** Assign the half open view [ @a b , @a e ) to @a this
+   *
+   * @param b First character in the view.
+   * @param e One character after the last character in the view.
+   * @return @a this
+   */
+  self_type &assign(char const *b, char const *e);
 
   /// Explicitly set the view from a @c std::string
   self_type &assign(std::string const &s);
@@ -900,6 +914,11 @@ TextView::operator=(const std::string &s) {
   return *this;
 }
 
+inline TextView&
+TextView::assign(char const *c_str) {
+  return this->assign(c_str, strlen(c_str));
+}
+
 inline TextView &
 TextView::assign(const std::string &s) {
   *this = super_type(s);
diff --git a/swoc++/include/swoc/swoc_ip.h b/swoc++/include/swoc/swoc_ip.h
index 0ee3f9b..4d40e3f 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,17 +12,29 @@
 #include <swoc/TextView.h>
 #include <swoc/DiscreteRange.h>
 #include <swoc/RBTree.h>
+#include <values.h>
 
-namespace swoc
-{
+namespace swoc {
 class IP4Addr;
+
 class IP6Addr;
+
 class IPAddr;
+
 class IPMask;
+
 class IP4Range;
+
 class IP6Range;
+
 class IPRange;
 
+class IP4Net;
+
+class IP6Net;
+
+class IPNet;
+
 using ::std::string_view;
 
 /** A union to hold @c sockaddr compliant IP address structures.
@@ -42,10 +55,12 @@ union IPEndpoint {
 
   /// Default construct invalid instance.
   IPEndpoint();
+
   /// Construct from the @a text representation of an address.
-  IPEndpoint(string_view const &text);
+  IPEndpoint(string_view const& text);
+
   // Construct from @a IPAddr
-  IPEndpoint(IPAddr const &addr);
+  IPEndpoint(IPAddr const& addr);
 
   /** Break a string in to IP address relevant tokens.
    *
@@ -58,7 +73,8 @@ union IPEndpoint {
    * Any of the out parameters can be @c nullptr in which case they are not 
updated.
    * This parses and discards the IPv6 brackets.
    */
-  static bool tokenize(string_view src, string_view *host = nullptr, 
string_view *port = nullptr, string_view *rest = nullptr);
+  static bool tokenize(string_view src, string_view *host = nullptr, 
string_view *port = nullptr
+                       , string_view *rest = nullptr);
 
   /** Parse a string for an IP address.
 
@@ -67,16 +83,16 @@ union IPEndpoint {
 
       @return @c true on success, @c false otherwise.
   */
-  bool parse(string_view const &str);
+  bool parse(string_view const& str);
 
   /// Invalidate a @c sockaddr.
   static void invalidate(sockaddr *addr);
 
   /// Invalidate this endpoint.
-  self_type &invalidate();
+  self_type& invalidate();
 
   /// Copy constructor.
-  self_type &operator=(self_type const &that);
+  self_type& operator=(self_type const& that);
 
   /** Copy (assign) the contents of @a src to @a dst.
    *
@@ -92,18 +108,20 @@ union IPEndpoint {
   /** Assign from a socket address.
       The entire address (all parts) are copied if the @a ip is valid.
   */
-  self_type &assign(sockaddr const *addr);
+  self_type& assign(sockaddr const *addr);
 
   /// Assign from an @a addr and @a port.
-  self_type &assign(IPAddr const &addr, in_port_t port = 0);
+  self_type& assign(IPAddr const& addr, in_port_t port = 0);
 
   /// Copy to @a sa.
-  const self_type &fill(sockaddr *addr) const;
+  const self_type& fill(sockaddr *addr) const;
 
   /// Test for valid IP address.
   bool is_valid() const;
+
   /// Test for IPv4.
   bool is_ip4() const;
+
   /// Test for IPv6.
   bool is_ip6() const;
 
@@ -119,28 +137,34 @@ union IPEndpoint {
   /// Set to be the ANY address for family @a family.
   /// @a family must be @c AF_INET or @c AF_INET6.
   /// @return This object.
-  self_type &set_to_any(int family);
+  self_type& set_to_any(int family);
 
   /// Set to be loopback address for family @a family.
   /// @a family must be @c AF_INET or @c AF_INET6.
   /// @return This object.
-  self_type &set_to_loopback(int family);
+  self_type& set_to_loopback(int family);
 
   /// Port in network order.
-  in_port_t &port();
+  in_port_t& port();
+
   /// Port in network order.
   in_port_t port() const;
+
   /// Port in host horder.
   in_port_t host_order_port() const;
+
   /// Port in network order from @a sockaddr.
-  static in_port_t &port(sockaddr *sa);
+  static in_port_t& port(sockaddr *sa);
+
   /// Port in network order from @a sockaddr.
   static in_port_t port(sockaddr const *sa);
+
   /// Port in host order directly from a @c sockaddr
   static in_port_t host_order_port(sockaddr const *sa);
 
   /// Automatic conversion to @c sockaddr.
   operator sockaddr *() { return &sa; }
+
   /// Automatic conversion to @c sockaddr.
   operator sockaddr const *() const { return &sa; }
 
@@ -154,55 +178,63 @@ union IPEndpoint {
 class IP4Addr {
   using self_type = IP4Addr; ///< Self reference type.
   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
   /// are in host order.
   explicit constexpr IP4Addr(in_addr_t addr);
+
   /// Construct from @c sockaddr_in.
   explicit IP4Addr(sockaddr_in const *sa);
+
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
-  IP4Addr(string_view const &text);
+  IP4Addr(string_view const& text);
+
   /// Construct from generic address @a addr.
   explicit IP4Addr(IPAddr const& addr);
 
   /// Assign from IPv4 raw address.
-  self_type &operator=(in_addr_t ip);
+  self_type& operator=(in_addr_t ip);
+
   /// Set to the address in @a addr.
-  self_type &operator=(sockaddr_in const *sa);
+  self_type& operator=(sockaddr_in const *sa);
 
   /// Increment address.
-  self_type &operator++();
+  self_type& operator++();
 
   /// Decrement address.
-  self_type &operator--();
+  self_type& operator--();
 
   /** Byte access.
    *
    * @param idx Byte index.
    * @return The byte at @a idx in the address.
    */
-  uint8_t operator [] (unsigned idx) const {
+  uint8_t operator[](unsigned idx) const {
     return reinterpret_cast<bytes const&>(_addr)[idx];
   }
 
   /// Apply @a mask to address, leaving the network portion.
-  self_type &operator&=(IPMask const& mask);
+  self_type& operator&=(IPMask const& mask);
+
   /// Apply @a mask to address, creating the broadcast address.
-  self_type &operator|=(IPMask const& mask);
+  self_type& operator|=(IPMask const& mask);
 
   /// Write this adddress and @a port to the sockaddr @a sa.
   sockaddr_in *fill(sockaddr_in *sa, in_port_t port = 0) const;
 
   /// @return The address in network order.
   in_addr_t network_order() const;
+
   /// @return The address in host order.
   in_addr_t host_order() const;
 
@@ -212,10 +244,10 @@ public:
 
       @return @c true on success, @c false otherwise.
   */
-  bool load(string_view const &text);
+  bool load(string_view const& text);
 
   /// Standard ternary compare.
-  int cmp(self_type const &that) const;
+  int cmp(self_type const& that) const;
 
   /// Get the IP address family.
   /// @return @c AF_INET
@@ -227,6 +259,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.
@@ -241,10 +305,13 @@ protected:
   /// Access by bytes.
   using bytes = std::array<uint8_t, 4>;
 
-  friend bool operator==(self_type const &, self_type const &);
-  friend bool operator!=(self_type const &, self_type const &);
-  friend bool operator<(self_type const &, self_type const &);
-  friend bool operator<=(self_type const &, self_type const &);
+  friend bool operator==(self_type const&, self_type const&);
+
+  friend bool operator!=(self_type const&, self_type const&);
+
+  friend bool operator<(self_type const&, self_type const&);
+
+  friend bool operator<=(self_type const&, self_type const&);
 
   in_addr_t _addr = INADDR_ANY; ///< Address in host order.
 };
@@ -255,18 +322,43 @@ 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,13 +366,16 @@ 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);
+  explicit IP6Addr(in6_addr const& addr);
+
   /// Construct from @c sockaddr_in.
   explicit IP6Addr(sockaddr_in6 const *addr) {
     *this = addr;
   }
+
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
   IP6Addr(string_view const& text);
@@ -288,22 +383,55 @@ 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++();
+  self_type& operator++();
 
   /// Decrement address.
-  self_type &operator--();
+  self_type& operator--();
 
   /// Assign from IPv6 raw address.
-  self_type &operator=(in6_addr const& ip);
+  self_type& operator=(in6_addr const& ip);
+
   /// Set to the address in @a addr.
-  self_type &operator=(sockaddr_in6 const *addr);
+  self_type& operator=(sockaddr_in6 const *addr);
 
   /// Write to @c sockaddr using network order and @a port.
   sockaddr *copy_to(sockaddr *sa, in_port_t port = 0) const;
 
   /// Copy address to @a addr in network order.
-  in6_addr & copy_to(in6_addr & addr) const;
+  in6_addr& copy_to(in6_addr& addr) const;
 
   /// Return the address in network order.
   in6_addr network_order() const;
@@ -315,10 +443,10 @@ public:
 
       @return @c true on success, @c false otherwise.
   */
-  bool load(string_view const &str);
+  bool load(string_view const& str);
 
   /// Generic compare.
-  int cmp(self_type const &that) const;
+  int cmp(self_type const& that) const;
 
   /// Get the address family.
   /// @return The address family.
@@ -330,44 +458,61 @@ public:
   /// Test for loopback
   bool is_multicast() const { return IN6_IS_ADDR_MULTICAST(_addr._raw.data()); 
}
 
-  self_type & clear() {
-    _addr._u64[0] = _addr._u64[1] = 0;
+  self_type& clear() {
+    _addr._store[0] = _addr._store[1] = 0;
     return *this;
   }
 
-  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]);
+  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);
 
 protected:
-  friend bool operator==(self_type const &, self_type const &);
-  friend bool operator!=(self_type const &, self_type const &);
-  friend bool operator<(self_type const &, self_type const &);
-  friend bool operator<=(self_type const &, self_type const &);
+  friend bool operator==(self_type const&, self_type const&);
+
+  friend bool operator!=(self_type const&, self_type const&);
+
+  friend bool operator<(self_type const&, self_type const&);
+
+  friend bool operator<=(self_type const&, self_type const&);
 
   /// 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 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.
  */
 class IPAddr {
   friend class IPRange;
+
   using self_type = IPAddr; ///< Self reference type.
 public:
   IPAddr() = default; ///< Default constructor - invalid result.
@@ -375,80 +520,99 @@ public:
 
   /// Construct using IPv4 @a addr.
   explicit IPAddr(in_addr_t addr);
+
   /// Construct using an IPv4 @a addr
-  IPAddr(IP4Addr const &addr) : _family(AF_INET), _addr{addr} {}
+  IPAddr(IP4Addr const& addr) : _family(AF_INET), _addr{addr} {}
+
   /// Construct using IPv6 @a addr.
-  explicit IPAddr(in6_addr const &addr);
+  explicit IPAddr(in6_addr const& addr);
+
   /// construct using an IPv6 @a addr
-  IPAddr(IP6Addr const &addr) : _family(AF_INET6), _addr{addr} {}
+  IPAddr(IP6Addr const& addr) : _family(AF_INET6), _addr{addr} {}
+
   /// Construct from @c sockaddr.
   explicit IPAddr(sockaddr const *addr);
+
   /// Construct from @c IPEndpoint.
-  explicit IPAddr(IPEndpoint const &addr);
+  explicit IPAddr(IPEndpoint const& addr);
+
   /// Construct from text representation.
   /// If the @a text is invalid the result is an invalid instance.
   explicit IPAddr(string_view const& text);
 
   /// Set to the address in @a addr.
-  self_type &assign(sockaddr const *addr);
+  self_type& assign(sockaddr const *addr);
+
   /// Set to the address in @a addr.
-  self_type &assign(sockaddr_in const *addr);
+  self_type& assign(sockaddr_in const *addr);
+
   /// Set to the address in @a addr.
-  self_type &assign(sockaddr_in6 const *addr);
+  self_type& assign(sockaddr_in6 const *addr);
+
   /// Set to the address in @a addr.
-  self_type &assign(in_addr_t addr);
+  self_type& assign(in_addr_t addr);
+
   /// Set to address in @a addr.
-  self_type &assign(in6_addr const &addr);
+  self_type& assign(in6_addr const& addr);
 
   /// Assign from end point.
-  self_type &operator=(IPEndpoint const &ip);
+  self_type& operator=(IPEndpoint const& ip);
+
   /// Assign from IPv4 raw address.
-  self_type &operator=(in_addr_t ip);
+  self_type& operator=(in_addr_t ip);
+
   /// Assign from IPv6 raw address.
-  self_type &operator=(in6_addr const &ip);
+  self_type& operator=(in6_addr const& ip);
+
   /// Assign from @c sockaddr
-  self_type &operator=(sockaddr const *addr);
+  self_type& operator=(sockaddr const *addr);
+
+  self_type& operator&=(IPMask const& mask);
 
-  self_type &operator&=(IPMask const& mask);
-  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.
+/** Parse a string and load the result in @a this.
    *
    * @param text Text to parse.
    * @return  @c true on success, @c false otherwise.
    */
-  bool load(string_view const &text);
+  bool load(string_view const& text);
 
   /// Generic compare.
-  int cmp(self_type const &that) const;
+  int cmp(self_type const& that) const;
 
   /// Test for same address family.
   /// @c return @c true if @a that is the same address family as @a this.
-  bool isCompatibleWith(self_type const &that);
+  bool isCompatibleWith(self_type const& that);
 
   /// Get the address family.
   /// @return The address family.
   sa_family_t family() const;
+
   /// Test for IPv4.
   bool is_ip4() const;
+
   /// Test for IPv6.
   bool is_ip6() const;
 
-  in_addr_t network_ip4() const;
-  in6_addr network_ip6() const;
+  IP4Addr const& ip4() const;
+
+  IP6Addr const& 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;
 
   /// Make invalid.
-  self_type &invalidate();
+  self_type& invalidate();
 
   /// Test for multicast
   bool is_multicast() const;
@@ -460,7 +624,8 @@ public:
   static self_type const INVALID;
 
 protected:
-  friend bool operator==(self_type const &, self_type const &);
+  friend bool operator==(self_type const&, self_type const&);
+
   friend IP4Addr;
   friend IP6Addr;
 
@@ -472,10 +637,13 @@ protected:
     uint64_t _u64[IP6Addr::SIZE / sizeof(uint64_t)]; ///< As 64 bit chunks.
 
     constexpr raw_addr_type();
+
     raw_addr_type(in_addr_t addr) : _ip4(addr) {}
+
     raw_addr_type(in6_addr const& addr) : _ip6(addr) {}
 
     raw_addr_type(IP4Addr const& addr) : _ip4(addr) {}
+
     raw_addr_type(IP6Addr const& addr) : _ip6(addr) {}
   } _addr;
 
@@ -488,36 +656,89 @@ 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;
+
+  /** Parse mask from @a text.
+   *
+   * @param text A number in string format.
+   * @return @a true if a valid CIDR value, @c false if not.
+   */
   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);
+
+  /// Force @a this to an invalid state.
+  self_type& clear() {
+    _cidr = INVALID;
+    return *this;
+  }
 
   /// 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;
+  }
 
-  /// Write the mask as an address to @a addr.
-  /// @return The filled address.
-  IPAddr &fill(IPAddr &addr);
+  self_type& operator>>=(raw_type n) {
+    _cidr += n;
+    return *this;
+  }
 
-private:
-  raw_type _mask{0};
-  sa_family_t _family{AF_UNSPEC};
-};
+  /** 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();
+
+  raw_type _cidr = INVALID; ///< Mask width in bits.
+
+  /// Compute a partial IPv6 mask, sized for the basic storage type.
+  static raw_type mask_for_quad(IP6Addr::quad_type q);
+};
 
 /** An inclusive range of IPv4 addresses.
  */
@@ -527,8 +748,6 @@ class IP4Range : public DiscreteRange<IP4Addr> {
   using metric_type = IP4Addr;
 
 public:
-  using super_type::super_type; ///< Import super class constructors.
-
   /// Default constructor, invalid range.
   IP4Range() = default;
 
@@ -549,13 +768,17 @@ public:
    */
   IP4Range(string_view const& text);
 
+  using super_type::super_type; ///< Import super class constructors.
+
   /** Set @a this range.
    *
    * @param addr Minimum address.
    * @param mask CIDR mask to compute maximum adddress from @a addr.
    * @return @a this
    */
-  self_type & assign(IP4Addr const& addr, IPMask const& mask);
+  self_type& assign(IP4Addr const& addr, IPMask const& mask);
+
+  using super_type::assign; ///< Import assign methods.
 
   /** Assign to this range from text.
    *
@@ -568,7 +791,86 @@ 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) const;
+
+  /// Inequality.
+  bool operator!=(self_type const& that) const;
+
 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> {
@@ -576,19 +878,31 @@ class IP6Range : public DiscreteRange<IP6Addr> {
   using super_type = DiscreteRange<IP6Addr>;
 
 public:
-  using super_type::super_type; ///< Import super class constructors.
-
   /// Construct from super type.
   /// @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);
+
+  using super_type::super_type; ///< Import super class constructors.
+
   /** Set @a this range.
    *
    * @param addr Minimum address.
    * @param mask CIDR mask to compute maximum adddress from @a addr.
    * @return @a this
    */
-  self_type & assign(IP6Addr const& addr, IPMask const& mask);
+  self_type& assign(IP6Addr const& addr, IPMask const& mask);
+
+  using super_type::assign; ///< Import assign methods.
 
   /** Assign to this range from text.
    *
@@ -601,6 +915,84 @@ 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 (IP6Range 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 { return _range.min(); }
+
+  /// Iterator support.
+  /// @return The current network mask.
+  IPMask mask() const { return _mask; }
+
+  /// Move to next network.
+  self_type& operator++();
+
+  /// Move to next network.
+  self_type operator++(int);
+
+  /// Equality.
+  bool operator==(self_type const& that) const;
+
+  /// Inequality.
+  bool operator!=(self_type const& that) const;
+
+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 {
@@ -608,10 +1000,15 @@ class IPRange {
 public:
   /// Default constructor - construct invalid range.
   IPRange() = default;
+
+  IPRange(IPAddr const& min, IPAddr const& max);
+
   /// Construct from an IPv4 @a range.
   IPRange(IP4Range const& range);
+
   /// Construct from an IPv6 @a range.
   IPRange(IP6Range const& range);
+
   /** Construct from a string format.
    *
    * @param text Text form of range.
@@ -620,12 +1017,18 @@ public:
    */
   IPRange(string_view const& text);
 
+  /// @return @c true if this is an IPv4 range, @c false if not.
+  bool is_ip4() const { return AF_INET == _family; }
+
+  /// @return @c true if this is an IPv6 range, @c false if not.
+  bool is_ip6() const { return AF_INET6 == _family; }
+
   /** Check if @a this range is the IP address @a family.
    *
    * @param family IP address family.
    * @return @c true if this is @a family, @c false if not.
    */
-  bool is(sa_family_t family) const ;
+  bool is(sa_family_t family) const;
 
   /** Load the range from @a text.
    *
@@ -639,15 +1042,34 @@ public:
 
   /// @return The minimum address in the range.
   IPAddr min() const;
+
   /// @return The maximum address in the range.
   IPAddr max() const;
 
   bool empty() const;
 
-  operator IP4Range & () { return _range._ip4; }
-  operator IP6Range & () { return _range._ip6; }
-  operator IP4Range const & () const { return _range._ip4; }
-  operator IP6Range const & () const { return _range._ip6; }
+  IP4Range const& ip4() const { return _range._ip4; }
+  IP6Range const& ip6() const { return _range._ip6; }
+
+  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 (IPRange const& range) {
+   *   for ( auto const& net : range ) {
+   *     net.addr(); // network address.
+   *     net.mask(); // network mask;
+   *   }
+   * }
+   * @endcode
+   */
+  NetSource networks() const;
 
 protected:
   /** Range container.
@@ -663,53 +1085,278 @@ protected:
     std::monostate _nil; ///< Make constructor easier to implement.
     IP4Range _ip4; ///< IPv4 range.
     IP6Range _ip6; ///< IPv6 range.
-  } _range { std::monostate{} };
+  } _range{std::monostate{}};
   /// Family of @a _range.
-  sa_family_t _family { AF_UNSPEC };
+  sa_family_t _family{AF_UNSPEC};
+};
+
+/** Network generator class.
+ * This generates networks from a range and acts as both a forward iterator 
and a container.
+ */
+class IPRange::NetSource {
+  using self_type = NetSource; ///< Self reference type.
+public:
+  using range_type = IPRange; ///< 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.
+  IPNet operator*() const;
+
+  /// Access @a this as if it were an @c IP6Net.
+  self_type *operator->();
+
+  /// Iterator support.
+  /// @areturn The current network address.
+  IPAddr 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) const;
+
+  /// Inequality.
+  bool operator!=(self_type const& that) const;
+
+protected:
+  union {
+    std::monostate _nil;
+    IP4Range::NetSource _ip4;
+    IP6Range::NetSource _ip6;
+  };
+  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);
+
+  IP4Net(swoc::TextView text) {
+    this->load(text);
+  }
+
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @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;
+
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type& assign(IP4Addr const& addr, IPMask const& mask);
+
+  /// Reset network to invalid state.
+  self_type& clear() {
+    _mask.clear();
+    return *this;
+  }
+
+  /// Equality.
+  bool operator==(self_type const& that) const;
+
+  /// Inequality
+  bool operator!=(self_type const& that) 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);
+
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
+
+  /// @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;
+
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type& assign(IP6Addr const& addr, IPMask const& mask);
+
+  /// Reset network to invalid state.
+  self_type& clear() {
+    _mask.clear();
+    return *this;
+  }
+
+  /// Equality.
+  bool operator==(self_type const& that) const;
+
+  /// Inequality
+  bool operator!=(self_type const& that) 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);
+  IPNet() = default; ///< Construct invalid network.
+  IPNet(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.
+   */
+  IPNet(IPAddr const& addr, IPMask const& mask);
 
-  operator IPAddr const &() const;
-  operator IPMask const &() const;
+  IPNet(TextView text);
 
-  IPAddr const &addr() const;
+  /** Parse network as @a text.
+   *
+   * @param text String describing the network in CIDR format.
+   * @return @c true if a valid string, @c false if not.
+   */
+  bool load(swoc::TextView text);
 
-  IPMask const &mask() const;
+  /// @return @c true if the network is valid, @c false if not.
+  bool is_valid() const;
 
+  /// @return THh smallest address in the network.
   IPAddr lower_bound() const;
+
+  /// @return The largest address in the network.
   IPAddr upper_bound() const;
 
-  bool contains(IPAddr const &addr) const;
+  IPMask::raw_type width() const;
+
+  /// @return The mask for the network.
+  IPMask const& mask() const;
 
-  // computes this is strict subset of other
-  bool is_subnet_of(self_type const &that);
+  /// @return A range that exactly covers the network.
+  IPRange as_range() const;
 
-  // Check if there are any addresses in both @a this and @a that.
-  bool intersects(self_type const &that);
+  bool is_ip4() const { return _addr.is_ip4(); }
+  bool is_ip6() const { return _addr.is_ip6(); }
 
-  self_type &assign(IPAddr const &addr, IPMask const &mask);
+  sa_family_t family() const { return _addr.family(); }
 
-  static char const SEPARATOR; // the character used between the address and 
mask
+  IP4Net ip4() const { return IP4Net{_addr.ip4(), _mask}; }
+  IP6Net ip6() const { return IP6Net{_addr.ip6(), _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;
+  /** Assign an @a addr and @a mask to @a this.
+   *
+   * @param addr Network addres.
+   * @param mask Network mask.
+   * @return @a this.
+   */
+  self_type& assign(IPAddr const& addr, IPMask const& mask);
+
+  /// Reset network to invalid state.
+  self_type& clear() {
+    _mask.clear();
+    return *this;
+  }
+
+  /// Equality.
+  bool operator==(self_type const& that) const;
 
-  static ::std::string ntoa(IpNet const &net); // DEPRECATED
-  static IpNet aton(::std::string const &str); // DEPRECATED - use ctor
+  /// Inequality
+  bool operator!=(self_type const& that) const;
 
 protected:
-  IPAddr _addr;
-  IPMask _mask;
+  IPAddr _addr; ///< Address and family.
+  IPMask _mask; ///< Network mask.
 };
 
 // --------------------------------------------------------------------------
@@ -726,7 +1373,7 @@ protected:
  * - Cheap to copy.
  * - Comparable via the equality and inequality operators.
  */
-template <typename PAYLOAD> class IPSpace {
+template<typename PAYLOAD> class IPSpace {
   using self_type = IPSpace;
   using IP4Space  = DiscreteSpace<IP4Addr, PAYLOAD>;
   using IP6Space  = DiscreteSpace<IP6Addr, PAYLOAD>;
@@ -745,7 +1392,7 @@ public:
    *
    * All addresses in @a r are set to have the @a payload.
    */
-  self_type & mark(IPRange const &range, PAYLOAD const &payload);
+  self_type& mark(IPRange const& range, PAYLOAD const& payload);
 
   /** Fill the @a range with @a payload.
    *
@@ -755,7 +1402,7 @@ public:
    *
    * Addresses in @a range are set to have @a payload if the address does not 
already have a payload.
    */
-  self_type & fill(IPRange const& range, PAYLOAD const& payload);
+  self_type& fill(IPRange const& range, PAYLOAD const& payload);
 
   /** Blend @a color in to the @a range.
    *
@@ -776,17 +1423,17 @@ public:
    * @c PAYLOAD @a p, @a p is updated by invoking <tt>blender(p, color)</tt>, 
with the expectation
    * that @a p will be updated in place.
    */
-  template < typename F , typename U = PAYLOAD >
-  self_type & blend(IPRange const& range, U const& color, F && blender);
+  template<typename F, typename U = PAYLOAD>
+  self_type& blend(IPRange const& range, U const& color, F&& blender);
 
-  template < typename F , typename U = PAYLOAD >
-  self_type & blend(IP4Range const& range, U const& color, F && blender) {
+  template<typename F, typename U = PAYLOAD>
+  self_type& blend(IP4Range const& range, U const& color, F&& blender) {
     _ip4.blend(range, color, blender);
     return *this;
   }
 
-  template < typename F , typename U = PAYLOAD >
-  self_type & blend(IP6Range const& range, U const& color, F && blender) {
+  template<typename F, typename U = PAYLOAD>
+  self_type& blend(IP6Range const& range, U const& color, F&& blender) {
     _ip6.blend(range, color, blender);
     return *this;
   }
@@ -796,7 +1443,7 @@ public:
    * @param addr Address to find.
    * @return The payload if any, @c nullptr if the address is not in the space.
    */
-  PAYLOAD *find(IP4Addr const &addr) {
+  PAYLOAD *find(IP4Addr const& addr) {
     return _ip4.find(addr);
   }
 
@@ -805,7 +1452,7 @@ public:
    * @param addr Address to find.
    * @return The payload if any, @c nullptr if the address is not in the space.
    */
-  PAYLOAD *find(IP6Addr const &addr) {
+  PAYLOAD *find(IP6Addr const& addr) {
     return _ip6.find(addr);
   }
 
@@ -814,7 +1461,7 @@ public:
    * @param addr Address to find.
    * @return The payload if any, @c nullptr if the address is not in the space.
    */
-  PAYLOAD *find(IPAddr const &addr) {
+  PAYLOAD *find(IPAddr const& addr) {
     if (addr.is_ip4()) {
       return _ip4.find(IP4Addr{addr});
     } else if (addr.is_ip6()) {
@@ -838,26 +1485,33 @@ public:
   class const_iterator {
     using self_type = const_iterator; ///< Self reference type.
     friend class IPSpace;
+
   public:
     using value_type = std::tuple<IPRange const, PAYLOAD const&>; /// Import 
for API compliance.
     // STL algorithm compliance.
     using iterator_category = std::bidirectional_iterator_tag;
     using pointer           = value_type *;
-    using reference         = value_type &;
+    using reference         = value_type&;
     using difference_type   = int;
 
     /// Default constructor.
     const_iterator() = default;
 
+    /// Copy constructor.
+    const_iterator(self_type const& that);
+
+    /// Assignment.
+    self_type & operator=(self_type const& that);
+
     /// Pre-increment.
     /// Move to the next element in the list.
     /// @return The iterator.
-    self_type &operator++();
+    self_type& operator++();
 
     /// Pre-decrement.
     /// Move to the previous element in the list.
     /// @return The iterator.
-    self_type &operator--();
+    self_type& operator--();
 
     /// Post-increment.
     /// Move to the next element in the list.
@@ -875,13 +1529,13 @@ public:
 
     /// Dereference.
     /// @return A pointer to the referent.
-    value_type const* operator->() const;
+    value_type const *operator->() const;
 
     /// Equality
-    bool operator==(self_type const &that) const;
+    bool operator==(self_type const& that) const;
 
     /// Inequality
-    bool operator!=(self_type const &that) const;
+    bool operator!=(self_type const& that) const;
 
   protected:
     // These are stored non-const to make implementing @c iterator easier. 
This class provides the
@@ -892,11 +1546,11 @@ public:
     typename IP4Space::iterator _iter_4; ///< IPv4 sub-space iterator.
     typename IP6Space::iterator _iter_6; ///< IPv6 sub-space iterator.
     /// Current value.
-    value_type _value { IPRange{}, *null_payload };
+    value_type _value{IPRange{}, *null_payload};
 
     /// Dummy payload.
     /// @internal Used to initialize @c value_type for invalid iterators.
-    static constexpr PAYLOAD *  null_payload = nullptr;
+    static constexpr PAYLOAD *null_payload = nullptr;
 
     /** Internal constructor.
      *
@@ -905,7 +1559,8 @@ public:
      *
      * In practice, both iterators should be either the beginning or ending 
iterator for the subspace.
      */
-    const_iterator(typename IP4Space::iterator const& iter4, typename 
IP6Space::iterator const& iter6);
+    const_iterator(typename IP4Space::iterator const& iter4
+                   , typename IP6Space::iterator const& iter6);
   };
 
   /** Iterator.
@@ -917,36 +1572,52 @@ public:
   class iterator : public const_iterator {
     using self_type = iterator;
     using super_type = const_iterator;
+
     friend class IPSpace;
+
   public:
   public:
     /// Value type of iteration.
     using value_type = std::tuple<IPRange const, PAYLOAD&>;
     using pointer           = value_type *;
-    using reference         = value_type &;
+    using reference         = value_type&;
 
     /// Default constructor.
     iterator() = default;
 
+    /// Copy constructor.
+    iterator(self_type const& that);
+
+    /// Assignment.
+    self_type & operator=(self_type const& that);
+
     /// Pre-increment.
     /// Move to the next element in the list.
     /// @return The iterator.
-    self_type &operator++();
+    self_type& operator++();
 
     /// Pre-decrement.
     /// Move to the previous element in the list.
     /// @return The iterator.
-    self_type &operator--();
+    self_type& operator--();
 
     /// Post-increment.
     /// Move to the next element in the list.
     /// @return The iterator value before the increment.
-    self_type operator++(int) { self_type zret{*this}; ++*this; return zret; }
+    self_type operator++(int) {
+      self_type zret{*this};
+      ++*this;
+      return zret;
+    }
 
     /// Post-decrement.
     /// Move to the previous element in the list.
     /// @return The iterator value before the decrement.
-    self_type operator--(int) { self_type zret{*this}; --*this; return zret; }
+    self_type operator--(int) {
+      self_type zret{*this};
+      --*this;
+      return zret;
+    }
 
     /// Dereference.
     /// @return A reference to the referent.
@@ -954,7 +1625,7 @@ public:
 
     /// Dereference.
     /// @return A pointer to the referent.
-    value_type const* operator->() const;
+    value_type const *operator->() const;
 
   protected:
     using super_type::super_type; /// Inherit supertype constructors.
@@ -962,10 +1633,12 @@ public:
 
   /// @return A constant iterator to the first element.
   const_iterator begin() const;
+
   /// @return A constent iterator past the last element.
   const_iterator end() const;
 
   iterator begin() { return iterator{_ip4.begin(), _ip6.begin()}; }
+
   iterator end() { return iterator{_ip4.end(), _ip6.end()}; }
 
 protected:
@@ -974,15 +1647,31 @@ protected:
 };
 
 template<typename PAYLOAD>
-IPSpace<PAYLOAD>::const_iterator::const_iterator(typename IP4Space::iterator 
const& iter4, typename IP6Space::iterator const& iter6) : _iter_4(iter4), 
_iter_6(iter6) {
+IPSpace<PAYLOAD>::const_iterator::const_iterator(typename IP4Space::iterator 
const& iter4
+                                                 , typename IP6Space::iterator 
const& iter6)
+    : _iter_4(iter4), _iter_6(iter6) {
   if (_iter_4.has_next()) {
     new(&_value) value_type{_iter_4->range(), _iter_4->payload()};
   } else if (_iter_6.has_next()) {
     new(&_value) value_type{_iter_6->range(), _iter_6->payload()};
   }
 }
+
+template<typename PAYLOAD>
+IPSpace<PAYLOAD>::const_iterator::const_iterator(self_type const& that) {
+  *this = that;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::const_iterator::operator=(self_type const& that) -> 
self_type &{
+  _iter_4 = that._iter_4;
+  _iter_6 = that._iter_6;
+  new(&_value) value_type{that._value};
+  return *this;
+}
+
 template<typename PAYLOAD>
-auto IPSpace<PAYLOAD>::const_iterator::operator++() -> self_type & {
+auto IPSpace<PAYLOAD>::const_iterator::operator++() -> self_type& {
   bool incr_p = false;
   if (_iter_4.has_next()) {
     ++_iter_4;
@@ -999,7 +1688,7 @@ auto IPSpace<PAYLOAD>::const_iterator::operator++() -> 
self_type & {
       return *this;
     }
   }
-  new (&_value) value_type{IPRange{}, *null_payload};
+  new(&_value) value_type{IPRange{}, *null_payload};
   return *this;
 }
 
@@ -1011,10 +1700,10 @@ auto IPSpace<PAYLOAD>::const_iterator::operator++(int) 
-> self_type {
 }
 
 template<typename PAYLOAD>
-auto IPSpace<PAYLOAD>::const_iterator::operator--() -> self_type & {
+auto IPSpace<PAYLOAD>::const_iterator::operator--() -> self_type& {
   if (_iter_6.has_prev()) {
     --_iter_6;
-    new (&_value) value_type{_iter_6->range(), _iter_6->payload()};
+    new(&_value) value_type{_iter_6->range(), _iter_6->payload()};
     return *this;
   }
   if (_iter_4.has_prev()) {
@@ -1022,7 +1711,7 @@ auto IPSpace<PAYLOAD>::const_iterator::operator--() -> 
self_type & {
     new(&_value) value_type{_iter_4->range(), _iter_4->payload()};
     return *this;
   }
-  new (&_value) value_type{IPRange{}, *null_payload};
+  new(&_value) value_type{IPRange{}, *null_payload};
   return *this;
 }
 
@@ -1060,8 +1749,19 @@ IPSpace<PAYLOAD>::const_iterator::operator!=(self_type 
const& that) const {
 }
 
 template<typename PAYLOAD>
-auto IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const* {
-  return static_cast<value_type*>(&super_type::_value);
+IPSpace<PAYLOAD>::iterator::iterator(self_type const& that) {
+  *this = that;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator=(self_type const& that) -> self_type 
& {
+  this->super_type::operator=(that);
+  return *this;
+}
+
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::iterator::operator->() const -> value_type const * {
+  return static_cast<value_type *>(&super_type::_value);
 }
 
 template<typename PAYLOAD>
@@ -1070,13 +1770,13 @@ auto IPSpace<PAYLOAD>::iterator::operator*() const -> 
value_type const& {
 }
 
 template<typename PAYLOAD>
-auto IPSpace<PAYLOAD>::iterator::operator++() -> self_type & {
+auto IPSpace<PAYLOAD>::iterator::operator++() -> self_type& {
   this->super_type::operator++();
   return *this;
 }
 
 template<typename PAYLOAD>
-auto IPSpace<PAYLOAD>::iterator::operator--() -> self_type & {
+auto IPSpace<PAYLOAD>::iterator::operator--() -> self_type& {
   this->super_type::operator--();
   return *this;
 }
@@ -1088,13 +1788,13 @@ inline constexpr IPAddr::raw_addr_type::raw_addr_type() 
: _ip4(INADDR_ANY) {}
 
 inline IPAddr::IPAddr(in_addr_t addr) : _family(AF_INET), _addr(addr) {}
 
-inline IPAddr::IPAddr(in6_addr const &addr) : _family(AF_INET6), _addr(addr) {}
+inline IPAddr::IPAddr(in6_addr const& addr) : _family(AF_INET6), _addr(addr) {}
 
 inline IPAddr::IPAddr(sockaddr const *addr) {
   this->assign(addr);
 }
 
-inline IPAddr::IPAddr(IPEndpoint const &addr) {
+inline IPAddr::IPAddr(IPEndpoint const& addr) {
   this->assign(&addr.sa);
 }
 
@@ -1102,26 +1802,26 @@ inline IPAddr::IPAddr(string_view const& text) {
   this->load(text);
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::operator=(in_addr_t addr) {
-  _family    = AF_INET;
+  _family = AF_INET;
   _addr._ip4 = addr;
   return *this;
 }
 
-inline IPAddr &
-IPAddr::operator=(in6_addr const &addr) {
-  _family    = AF_INET6;
+inline IPAddr&
+IPAddr::operator=(in6_addr const& addr) {
+  _family = AF_INET6;
   _addr._ip6 = addr;
   return *this;
 }
 
-inline IPAddr &
-IPAddr::operator=(IPEndpoint const &addr) {
+inline IPAddr&
+IPAddr::operator=(IPEndpoint const& addr) {
   return this->assign(&addr.sa);
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::operator=(sockaddr const *addr) {
   return this->assign(addr);
 }
@@ -1142,56 +1842,53 @@ IPAddr::is_ip6() const {
 }
 
 inline bool
-IPAddr::isCompatibleWith(self_type const &that) {
+IPAddr::isCompatibleWith(self_type const& that) {
   return this->is_valid() && _family == that._family;
 }
 
 inline bool
 IPAddr::is_loopback() const {
-  return (AF_INET == _family && 0x7F == _addr._octet[0]) || (AF_INET6 == 
_family && IN6_IS_ADDR_LOOPBACK(&_addr._ip6));
+  return (AF_INET == _family && 0x7F == _addr._octet[0]) ||
+         (AF_INET6 == _family && IN6_IS_ADDR_LOOPBACK(&_addr._ip6));
 }
 
 inline bool
-operator==(IPAddr const &lhs, IPAddr const &rhs) {
-  if (lhs._family != rhs._family)
+operator==(IPAddr const& lhs, IPAddr const& rhs) {
+  if (lhs._family != rhs._family) {
     return false;
-  switch (lhs._family)
-  {
-  case AF_INET:
-    return lhs._addr._ip4 == rhs._addr._ip4;
-  case AF_INET6:
-    return 0 == memcmp(&lhs._addr._ip6, &rhs._addr._ip6, IP6Addr::SIZE);
-  case AF_UNSPEC:
-    return true;
-  default:
-    break;
+  }
+  switch (lhs._family) {
+    case AF_INET:return lhs._addr._ip4 == rhs._addr._ip4;
+    case AF_INET6:return 0 == memcmp(&lhs._addr._ip6, &rhs._addr._ip6, 
IP6Addr::SIZE);
+    case AF_UNSPEC:return true;
+    default:break;
   }
   return false;
 }
 
 inline bool
-operator!=(IPAddr const &lhs, IPAddr const &rhs) {
+operator!=(IPAddr const& lhs, IPAddr const& rhs) {
   return !(lhs == rhs);
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::assign(in_addr_t addr) {
-  _family    = AF_INET;
+  _family = AF_INET;
   _addr._ip4 = addr;
   return *this;
 }
 
-inline IPAddr &
-IPAddr::assign(in6_addr const &addr) {
-  _family    = AF_INET6;
+inline IPAddr&
+IPAddr::assign(in6_addr const& addr) {
+  _family = AF_INET6;
   _addr._ip6 = addr;
   return *this;
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::assign(sockaddr_in const *addr) {
   if (addr) {
-    _family    = AF_INET;
+    _family = AF_INET;
     _addr._ip4 = addr;
   } else {
     _family = AF_UNSPEC;
@@ -1199,10 +1896,10 @@ IPAddr::assign(sockaddr_in const *addr) {
   return *this;
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::assign(sockaddr_in6 const *addr) {
   if (addr) {
-    _family    = AF_INET6;
+    _family = AF_INET6;
     _addr._ip6 = addr->sin6_addr;
   } else {
     _family = AF_UNSPEC;
@@ -1215,72 +1912,73 @@ IPAddr::is_valid() const {
   return _family == AF_INET || _family == AF_INET6;
 }
 
-inline IPAddr &
+inline IPAddr&
 IPAddr::invalidate() {
   _family = AF_UNSPEC;
   return *this;
 }
 
 // Associated operators.
-bool operator==(IPAddr const &lhs, sockaddr const *rhs);
+bool operator==(IPAddr const& lhs, sockaddr const *rhs);
+
 inline bool
-operator==(sockaddr const *lhs, IPAddr const &rhs) {
+operator==(sockaddr const *lhs, IPAddr const& rhs) {
   return rhs == lhs;
 }
+
 inline bool
-operator!=(IPAddr const &lhs, sockaddr const *rhs) {
+operator!=(IPAddr const& lhs, sockaddr const *rhs) {
   return !(lhs == rhs);
 }
+
 inline bool
-operator!=(sockaddr const *lhs, IPAddr const &rhs) {
+operator!=(sockaddr const *lhs, IPAddr const& rhs) {
   return !(rhs == lhs);
 }
+
 inline bool
-operator==(IPAddr const &lhs, IPEndpoint const &rhs) {
+operator==(IPAddr const& lhs, IPEndpoint const& rhs) {
   return lhs == &rhs.sa;
 }
+
 inline bool
-operator==(IPEndpoint const &lhs, IPAddr const &rhs) {
+operator==(IPEndpoint const& lhs, IPAddr const& rhs) {
   return &lhs.sa == rhs;
 }
+
 inline bool
-operator!=(IPAddr const &lhs, IPEndpoint const &rhs) {
+operator!=(IPAddr const& lhs, IPEndpoint const& rhs) {
   return !(lhs == &rhs.sa);
 }
+
 inline bool
-operator!=(IPEndpoint const &lhs, IPAddr const &rhs) {
+operator!=(IPEndpoint const& lhs, IPAddr const& rhs) {
   return !(rhs == &lhs.sa);
 }
 
 inline bool
-operator<(IPAddr const &lhs, IPAddr const &rhs) {
+operator<(IPAddr const& lhs, IPAddr const& rhs) {
   return -1 == lhs.cmp(rhs);
 }
 
 inline bool
-operator>=(IPAddr const &lhs, IPAddr const &rhs) {
+operator>=(IPAddr const& lhs, IPAddr const& rhs) {
   return lhs.cmp(rhs) >= 0;
 }
 
 inline bool
-operator>(IPAddr const &lhs, IPAddr const &rhs) {
+operator>(IPAddr const& lhs, IPAddr const& rhs) {
   return 1 == lhs.cmp(rhs);
 }
 
 inline bool
-operator<=(IPAddr const &lhs, IPAddr const &rhs) {
+operator<=(IPAddr const& lhs, IPAddr const& rhs) {
   return lhs.cmp(rhs) <= 0;
 }
 
-inline in_addr_t
-IPAddr::network_ip4() const {
-  return _addr._ip4.network_order();
-}
+inline IP4Addr const& IPAddr::ip4() const { return _addr._ip4; }
 
-inline in6_addr
-IPAddr::network_ip6() const {
-  return _addr._ip6.network_order();
-}
+inline IP6Addr const& IPAddr::ip6() const { return _addr._ip6; }
 
 /// 
------------------------------------------------------------------------------------
 
@@ -1288,11 +1986,11 @@ inline IPEndpoint::IPEndpoint() {
   sa.sa_family = AF_UNSPEC;
 }
 
-inline IPEndpoint::IPEndpoint(IPAddr const &addr) {
+inline IPEndpoint::IPEndpoint(IPAddr const& addr) {
   this->assign(addr);
 }
 
-inline IPEndpoint &
+inline IPEndpoint&
 IPEndpoint::invalidate() {
   sa.sa_family = AF_UNSPEC;
   return *this;
@@ -1308,19 +2006,19 @@ IPEndpoint::is_valid() const {
   return sa.sa_family == AF_INET || sa.sa_family == AF_INET6;
 }
 
-inline IPEndpoint &
-IPEndpoint::operator=(self_type const &that) {
+inline IPEndpoint&
+IPEndpoint::operator=(self_type const& that) {
   self_type::assign(&sa, &that.sa);
   return *this;
 }
 
-inline IPEndpoint &
+inline IPEndpoint&
 IPEndpoint::assign(sockaddr const *src) {
   self_type::assign(&sa, src);
   return *this;
 }
 
-inline IPEndpoint const &
+inline IPEndpoint const&
 IPEndpoint::fill(sockaddr *addr) const {
   self_type::assign(addr, &sa);
   return *this;
@@ -1341,7 +2039,7 @@ IPEndpoint::family() const {
   return sa.sa_family;
 }
 
-inline in_port_t &
+inline in_port_t&
 IPEndpoint::port() {
   return self_type::port(&sa);
 }
@@ -1356,14 +2054,11 @@ IPEndpoint::host_order_port() const {
   return ntohs(this->port());
 }
 
-inline in_port_t &
+inline in_port_t&
 IPEndpoint::port(sockaddr *sa) {
-  switch (sa->sa_family)
-  {
-  case AF_INET:
-    return reinterpret_cast<sockaddr_in *>(sa)->sin_port;
-  case AF_INET6:
-    return reinterpret_cast<sockaddr_in6 *>(sa)->sin6_port;
+  switch (sa->sa_family) {
+    case AF_INET:return reinterpret_cast<sockaddr_in *>(sa)->sin_port;
+    case AF_INET6:return reinterpret_cast<sockaddr_in6 *>(sa)->sin6_port;
   }
   // Force a failure upstream by returning a null reference.
   return *static_cast<in_port_t *>(nullptr);
@@ -1384,20 +2079,41 @@ IPEndpoint::host_order_port(sockaddr const *sa) {
 inline constexpr IP4Addr::IP4Addr(in_addr_t addr) : _addr(addr) {}
 
 inline IP4Addr::IP4Addr(std::string_view const& text) {
-  if (! this->load(text)) {
+  if (!this->load(text)) {
     _addr = INADDR_ANY;
   }
 }
 
-inline IP4Addr::IP4Addr(IPAddr const& addr) : _addr(addr._family == AF_INET ? 
addr._addr._ip4._addr : INADDR_ANY) {}
+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 &
+inline IP4Addr&
 IP4Addr::operator++() {
   ++_addr;
   return *this;
 }
 
-inline IP4Addr &
+inline IP4Addr&
 IP4Addr::operator--() {
   --_addr;
   return *this;
@@ -1412,51 +2128,48 @@ inline in_addr_t IP4Addr::host_order() const {
 }
 
 inline auto
-IP4Addr::operator=(in_addr_t ip) -> self_type & {
+IP4Addr::operator=(in_addr_t ip) -> self_type& {
   _addr = ntohl(ip);
   return *this;
 }
 
-inline bool operator == (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator==(IP4Addr const& lhs, IP4Addr const& rhs) {
   return lhs._addr == rhs._addr;
 }
 
-inline bool operator != (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator!=(IP4Addr const& lhs, IP4Addr const& rhs) {
   return lhs._addr != rhs._addr;
 }
 
-inline bool operator < (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator<(IP4Addr const& lhs, IP4Addr const& rhs) {
   return lhs._addr < rhs._addr;
 }
 
-inline bool operator <= (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator<=(IP4Addr const& lhs, IP4Addr const& rhs) {
   return lhs._addr <= rhs._addr;
 }
 
-inline bool operator > (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator>(IP4Addr const& lhs, IP4Addr const& rhs) {
   return rhs < lhs;
 }
 
-inline bool operator >= (IP4Addr const& lhs, IP4Addr const& rhs) {
+inline bool operator>=(IP4Addr const& lhs, IP4Addr const& rhs) {
   return rhs <= lhs;
 }
 
-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;
+inline IP4Addr& IP4Addr::operator&=(IPMask const& mask) {
+  _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;
+inline IP4Addr& IP4Addr::operator|=(IPMask const& mask) {
+  _addr |= ~(mask.as_ip4()._addr);
   return *this;
 }
 
 constexpr in_addr_t IP4Addr::reorder(in_addr_t src) {
-  return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 
0xFF) << 8) | ((src >> 24) & 0xFF);
+  return ((src & 0xFF) << 24) | (((src >> 8) & 0xFF) << 16) | (((src >> 16) & 
0xFF) << 8) |
+         ((src >> 24) & 0xFF);
 }
 
 // ---
@@ -1466,14 +2179,14 @@ inline IP6Addr::IP6Addr(in6_addr const& addr) {
 }
 
 inline IP6Addr::IP6Addr(std::string_view const& text) {
-  if (! this->load(text)) {
+  if (!this->load(text)) {
     this->clear();
   }
 }
 
 inline IP6Addr::IP6Addr(IPAddr const& addr) : _addr{addr._addr._ip6._addr} {}
 
-inline in6_addr& IP6Addr::copy_to(in6_addr & addr) const {
+inline in6_addr& IP6Addr::copy_to(in6_addr& addr) const {
   self_type::reorder(addr, _addr._raw);
   return addr;
 }
@@ -1483,98 +2196,120 @@ inline in6_addr IP6Addr::network_order() const {
   return this->copy_to(zret);
 }
 
-inline auto IP6Addr::operator = (in6_addr const& addr) -> self_type & {
+inline auto IP6Addr::operator=(in6_addr const& addr) -> self_type& {
   self_type::reorder(_addr._raw, addr);
   return *this;
 }
 
-inline auto IP6Addr::operator = (sockaddr_in6 const* addr) -> self_type & {
+inline auto IP6Addr::operator=(sockaddr_in6 const *addr) -> self_type& {
   if (addr) {
     return *this = addr->sin6_addr;
   }
   this->clear();
 }
 
-inline IP6Addr &
+inline IP6Addr&
 IP6Addr::operator++() {
-  if (++(_addr._u64[1]) == 0) {
-    ++(_addr._u64[0]);
+  if (++(_addr._store[1]) == 0) {
+    ++(_addr._store[0]);
   }
   return *this;
 }
 
-inline IP6Addr &
+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];
+inline bool operator==(IP6Addr const& lhs, IP6Addr const& rhs) {
+  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];
+inline bool operator!=(IP6Addr const& lhs, IP6Addr const& rhs) {
+  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]);
+inline bool operator<(IP6Addr const& lhs, IP6Addr const& rhs) {
+  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) {
+inline bool operator>(IP6Addr const& lhs, IP6Addr const& rhs) {
   return rhs < lhs;
 }
 
-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]);
+inline bool operator<=(IP6Addr const& lhs, IP6Addr const& rhs) {
+  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) {
+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) {
+inline bool operator==(IPAddr const& lhs, IP4Addr const& rhs) {
   return lhs.is_ip4() && static_cast<IP4Addr const&>(lhs) == rhs;
 }
 
-inline bool operator != (IPAddr const& lhs, IP4Addr const& rhs) {
-  return ! lhs.is_ip4() || static_cast<IP4Addr const&>(lhs) != rhs;
+inline bool operator!=(IPAddr const& lhs, IP4Addr const& rhs) {
+  return !lhs.is_ip4() || static_cast<IP4Addr const&>(lhs) != rhs;
 }
 
-inline bool operator == (IP4Addr const& lhs, IPAddr const& rhs) {
+inline bool operator==(IP4Addr const& lhs, IPAddr const& rhs) {
   return rhs.is_ip4() && lhs == static_cast<IP4Addr const&>(rhs);
 }
 
-inline bool operator != (IP4Addr const& lhs, IPAddr const& rhs) {
-  return ! rhs.is_ip4() || lhs != static_cast<IP4Addr const&>(rhs);
+inline bool operator!=(IP4Addr const& lhs, IPAddr const& rhs) {
+  return !rhs.is_ip4() || lhs != static_cast<IP4Addr const&>(rhs);
 }
 
-inline bool operator == (IPAddr const& lhs, IP6Addr const& rhs) {
+inline bool operator==(IPAddr const& lhs, IP6Addr const& rhs) {
   return lhs.is_ip6() && static_cast<IP6Addr const&>(lhs) == rhs;
 }
 
-inline bool operator != (IPAddr const& lhs, IP6Addr const& rhs) {
-  return ! lhs.is_ip6() || static_cast<IP6Addr const&>(lhs) != rhs;
+inline bool operator!=(IPAddr const& lhs, IP6Addr const& rhs) {
+  return !lhs.is_ip6() || static_cast<IP6Addr const&>(lhs) != rhs;
 }
 
-inline bool operator == (IP6Addr const& lhs, IPAddr const& rhs) {
+inline bool operator==(IP6Addr const& lhs, IPAddr const& rhs) {
   return rhs.is_ip6() && lhs == static_cast<IP6Addr const&>(rhs);
 }
 
-inline bool operator != (IP6Addr const& lhs, IPAddr const& rhs) {
-  return ! rhs.is_ip6() || lhs != static_cast<IP6Addr const&>(rhs);
+inline bool operator!=(IP6Addr const& lhs, IPAddr const& rhs) {
+  return !rhs.is_ip6() || lhs != static_cast<IP6Addr const&>(rhs);
 }
 
 // +++ IPRange +++
@@ -1583,6 +2318,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;
 }
@@ -1595,81 +2342,334 @@ inline IPRange::IPRange(string_view const& text) {
   this->load(text);
 }
 
+inline auto IPRange::networks() const -> NetSource {
+  return {NetSource{*this}};
+}
+
 inline bool IPRange::is(sa_family_t family) const { return family == _family; }
 
-// +++ IpMask +++
+// +++ IPMask +++
+
+inline IPMask::IPMask(raw_type width) : _cidr(width) {}
 
-inline IPMask::IPMask(raw_type width, sa_family_t family) : _mask(width), 
_family(family) {}
+inline bool IPMask::is_valid() const { return _cidr < INVALID; }
 
-inline IPMask::raw_type
-IPMask::width() const {
-  return _mask;
+inline auto IPMask::width() const -> raw_type {
+  return _cidr;
 }
 
 inline bool
-operator==(IPMask const &lhs, IPMask const &rhs) {
+operator==(IPMask const& lhs, IPMask const& rhs) {
   return lhs.width() == rhs.width();
 }
 
 inline bool
-operator!=(IPMask const &lhs, IPMask const &rhs) {
+operator!=(IPMask const& lhs, IPMask const& rhs) {
   return lhs.width() != rhs.width();
 }
 
 inline bool
-operator<(IPMask const &lhs, IPMask const &rhs) {
+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 IpNet::IpNet(IPAddr const &addr, IPMask const &mask) : _addr(addr), 
_mask(mask) {}
+inline IP4Addr operator&(IP4Addr const& addr, IPMask const& mask) {
+  return IP4Addr{addr} &= mask;
+}
 
-inline IpNet::operator IPAddr const &() const {
-  return _addr;
+inline IP4Addr operator|(IP4Addr const& addr, IPMask const& mask) {
+  return IP4Addr{addr} |= mask;
 }
 
-inline IpNet::operator IPMask const &() const {
-  return _mask;
+inline IP6Addr operator&(IP6Addr const& addr, IPMask const& mask) {
+  return IP6Addr{addr} &= mask;
 }
 
-inline IPAddr const &
-IpNet::addr() const {
-  return _addr;
+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 IPAddr operator|(IPAddr const& addr, IPMask const& mask) {
+  return IPAddr{addr} |= mask;
+}
+
+// +++ 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 bool IP4Net::operator==(self_type const& that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool IP4Net::operator!=(self_type const& that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline IP4Net::self_type& IP4Net::assign(IP4Addr const& addr, IPMask const& 
mask) {
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+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 bool IP6Net::operator==(self_type const& that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool IP6Net::operator!=(self_type const& that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline IP6Net::self_type& IP6Net::assign(IP6Addr const& addr, IPMask const& 
mask) {
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+inline IPNet::IPNet(IPAddr const& addr, IPMask const& mask) : _addr(addr & 
mask), _mask(mask) {}
+
+inline IPNet::IPNet(TextView text) { this->load(text); }
+
+inline bool IPNet::is_valid() const { return _mask.is_valid(); }
+
+inline IPAddr IPNet::lower_bound() const { return _addr; }
+
+inline IPAddr IPNet::upper_bound() const { return _addr | _mask; }
+
+inline IPMask::raw_type IPNet::width() const { return _mask.width(); }
+
+inline IPMask const& IPNet::mask() const { return _mask; }
+
+inline IPRange IPNet::as_range() const { return {this->lower_bound(), 
this->upper_bound()};}
+
+inline IPNet::self_type& IPNet::assign(IPAddr const& addr, IPMask const& mask) 
{
+  _addr = addr & mask;
+  _mask = mask;
+  return *this;
+}
+
+inline bool IPNet::operator==(IPNet::self_type const& that) const {
+  return _mask == that._mask && _addr == that._addr;
+}
+
+inline bool IPNet::operator!=(IPNet::self_type const& that) const {
+  return _mask != that._mask || _addr != that._addr;
+}
+
+inline bool operator == (IPNet const& lhs, IP4Net const& rhs) {
+  return lhs.is_ip4() && lhs.ip4() == rhs;
+}
+
+inline bool operator == (IP4Net const& lhs, IPNet const& rhs) {
+  return rhs.is_ip4() && rhs.ip4() == lhs;
+}
+
+inline bool operator == (IPNet const& lhs, IP6Net const& rhs) {
+  return lhs.is_ip6() && lhs.ip6() == rhs;
+}
+
+inline bool operator == (IP6Net const& lhs, IPNet const& rhs) {
+  return rhs.is_ip6() && rhs.ip6() == lhs;
+}
+
+// +++ 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) const {
+  return ((_cidr == that._cidr) && (_range == that._range)) ||
+         (_range.empty() && that._range.empty());
+}
+
+inline bool IP4Range::NetSource::operator!=(IP4Range::NetSource::self_type 
const& that) const {
+  return !(*this == that);
+}
+
+inline auto IP6Range::NetSource::begin() const -> iterator {
+  return *this;
+}
+
+inline auto IP6Range::NetSource::end() const -> iterator {
+  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) const {
+  return ((_mask == that._mask) && (_range == that._range)) ||
+         (_range.empty() && that._range.empty());
+}
+
+inline bool IP6Range::NetSource::operator!=(IP6Range::NetSource::self_type 
const& that) const {
+  return !(*this == that);
+}
+
+inline IPRange::NetSource::NetSource(IPRange::NetSource::range_type const& 
range) {
+  if (range.is_ip4()) {
+    new (&_ip4) decltype(_ip4)(range.ip4());
+    _family = AF_INET;
+  } else if (range.is_ip6()) {
+    new (&_ip6) decltype(_ip6)(range.ip6());
+    _family = AF_INET6;
+  }
+}
+
+inline auto IPRange::NetSource::begin() const -> iterator {
+  return *this;
+}
+
+inline auto IPRange::NetSource::end() const -> iterator {
+  return AF_INET == _family ? self_type{IP4Range{}}
+  : AF_INET6 == _family ? self_type{IP6Range{}}
+  : self_type{IPRange{}};
+}
+
+inline IPAddr IPRange::NetSource::addr() const {
+  if (AF_INET == _family) {
+    return _ip4.addr();
+  } else if (AF_INET6 == _family) {
+    return _ip6.addr();
+  }
+  return {};
 }
 
-inline IPMask const &
-IpNet::mask() const {
-  return _mask;
+inline IPMask IPRange::NetSource::mask() const {
+  if (AF_INET == _family) {
+    return _ip4.mask();
+  } else if (AF_INET6 == _family) {
+    return _ip6.mask();
+  }
+  return {};
+}
+
+inline IPNet IPRange::NetSource::operator*() const {
+  return { this->addr(), this->mask() };
+}
+
+inline auto IPRange::NetSource::operator++() -> self_type & {
+  if (AF_INET == _family) {
+    ++_ip4;
+  } else if (AF_INET6 == _family) {
+    ++_ip6;
+  }
+  return *this;
+}
+
+inline auto IPRange::NetSource::operator->() -> self_type * { return this; }
+
+inline bool IPRange::NetSource::operator==(self_type const& that) const {
+  if (_family != that._family) {
+    return false;
+  }
+  if (AF_INET == _family) {
+    return _ip4 == that._ip4;
+  } else if (AF_INET6 == _family) {
+    return _ip6 == that._ip6;
+  } else if (AF_UNSPEC == _family) {
+    return true;
+  }
+  return false;
+}
+
+inline bool IPRange::NetSource::operator!=(self_type const& that) const {
+  return !(*this == that);
 }
 
 // --- IPSpace
 
-template < typename PAYLOAD > auto IPSpace<PAYLOAD>::mark(IPRange const 
&range, PAYLOAD const &payload) -> self_type & {
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::mark(IPRange const& range, PAYLOAD const& payload) -> 
self_type& {
   if (range.is(AF_INET)) {
-    _ip4.mark(range, payload);
+    _ip4.mark(range.ip4(), payload);
   } else if (range.is(AF_INET6)) {
-    _ip6.mark(range, payload);
+    _ip6.mark(range.ip6(), payload);
   }
   return *this;
 }
 
-template < typename PAYLOAD > auto IPSpace<PAYLOAD>::fill(IPRange const 
&range, PAYLOAD const &payload) -> self_type & {
+template<typename PAYLOAD>
+auto IPSpace<PAYLOAD>::fill(IPRange const& range, PAYLOAD const& payload) -> 
self_type& {
   if (range.is(AF_INET6)) {
-    _ip6.fill(range, payload);
+    _ip6.fill(range.ip6(), payload);
   } else if (range.is(AF_INET)) {
-    _ip4.fill(range, payload);
+    _ip4.fill(range.ip4(), payload);
   }
   return *this;
 }
 
 template<typename PAYLOAD>
-template<typename F, typename U >
-auto IPSpace<PAYLOAD>::blend(IPRange const&range, U const&color, F&&blender) 
-> self_type & {
+template<typename F, typename U>
+auto IPSpace<PAYLOAD>::blend(IPRange const& range, U const& color, F&& 
blender) -> self_type& {
   if (range.is(AF_INET)) {
-    _ip4.blend(range, color, blender);
+    _ip4.blend(range.ip4(), color, blender);
   } else if (range.is(AF_INET6)) {
-    _ip6.blend(range, color, blender);
+    _ip6.blend(range.ip6(), color, blender);
   }
   return *this;
 }
@@ -1682,15 +2682,103 @@ void IPSpace<PAYLOAD>::clear() {
 
 template<typename PAYLOAD>
 auto IPSpace<PAYLOAD>::begin() const -> const_iterator {
-  auto nc_this = const_cast<self_type*>(this);
+  auto nc_this = const_cast<self_type *>(this);
   return const_iterator(nc_this->_ip4.begin(), nc_this->_ip6.begin());
 }
 
 template<typename PAYLOAD>
 auto IPSpace<PAYLOAD>::end() const -> const_iterator {
-  auto nc_this = const_cast<self_type*>(this);
+  auto nc_this = const_cast<self_type *>(this);
   return const_iterator(nc_this->_ip4.end(), nc_this->_ip6.end());
 }
 
+} // namespace swoc
+
+namespace std {
+
+// -- Tuple support for IP4Net --
+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;
+};
+
+// -- Tuple support for IP6Net --
+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;
+};
+
+// -- Tuple support for IPNet --
+template<> class tuple_size<swoc::IPNet> : public 
std::integral_constant<size_t, 2> {
+};
+
+template<size_t IDX> class tuple_element<IDX, swoc::IPNet> {
+  static_assert("swoc::IPNet tuple index out of range");
+};
+
+template<> class tuple_element<0, swoc::IPNet> {
+public:
+  using type = swoc::IPAddr;
+};
+
+template<> class tuple_element<1, swoc::IPNet> {
+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();
+  }
+}
+
+template<size_t IDX> typename std::tuple_element<IDX, IPNet>::type
+get(swoc::IPNet 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/bw_ip_format.cc b/swoc++/src/bw_ip_format.cc
index e0e8b58..9da2a77 100644
--- a/swoc++/src/bw_ip_format.cc
+++ b/swoc++/src/bw_ip_format.cc
@@ -280,7 +280,7 @@ bwformat(BufferWriter &w, Spec const &spec, IPAddr const 
&addr)
     if (addr.is_ip4()) {
       swoc::bwformat(w, spec, static_cast<IP4Addr const&>(addr));
     } else if (addr.is_ip6()) {
-      swoc::bwformat(w, spec, addr.network_ip6());
+      swoc::bwformat(w, spec, addr.ip6().network_order());
     } else {
       w.print("*Not IP address [{}]*", addr.family());
     }
@@ -317,9 +317,9 @@ bwformat(BufferWriter & w, Spec const& spec, IP6Range 
const& range) {
 BufferWriter &
 bwformat(BufferWriter & w, Spec const& spec, IPRange const& range) {
   return range.is(AF_INET)
-  ? bwformat(w, spec, static_cast<IP4Range const&>(range))
+  ? bwformat(w, spec, range.ip4())
   : range.is(AF_INET6)
-    ? bwformat(w, spec, static_cast<IP6Range const&>(range))
+    ? bwformat(w, spec, range.ip6())
     : w.write("*-*"_tv)
   ;
 }
diff --git a/swoc++/src/swoc_ip.cc b/swoc++/src/swoc_ip.cc
index 7fd4264..59075a6 100644
--- a/swoc++/src/swoc_ip.cc
+++ b/swoc++/src/swoc_ip.cc
@@ -81,7 +81,7 @@ IPEndpoint::assign(IPAddr const&src, in_port_t port) {
     case AF_INET: {
       memset(&sa4, 0, sizeof sa4);
       sa4.sin_family = AF_INET;
-      sa4.sin_addr.s_addr = src.network_ip4();
+      sa4.sin_addr.s_addr = src.ip4().network_order();
       sa4.sin_port = port;
       Set_Sockaddr_Len(&sa4);
     }
@@ -89,7 +89,7 @@ IPEndpoint::assign(IPAddr const&src, in_port_t port) {
     case AF_INET6: {
       memset(&sa6, 0, sizeof sa6);
       sa6.sin6_family = AF_INET6;
-      sa6.sin6_addr = src.network_ip6();
+      sa6.sin6_addr = src.ip6().network_order();
       sa6.sin6_port = port;
       Set_Sockaddr_Len(&sa6);
     }
@@ -98,19 +98,6 @@ IPEndpoint::assign(IPAddr const&src, in_port_t port) {
   return *this;
 }
 
-IPAddr&
-IPAddr::assign(sockaddr const *addr) {
-  if (addr) {
-    switch (addr->sa_family) {
-      case AF_INET:return this->assign(reinterpret_cast<sockaddr_in const 
*>(addr));
-      case AF_INET6:return this->assign(reinterpret_cast<sockaddr_in6 const 
*>(addr));
-      default:break;
-    }
-  }
-  _family = AF_UNSPEC;
-  return *this;
-}
-
 bool
 IPEndpoint::tokenize(std::string_view str, std::string_view *addr, 
std::string_view *port
                      , std::string_view *rest) {
@@ -190,6 +177,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 +199,7 @@ IPAddr::fill(sockaddr *sa, in_port_t port) const {
   }
   return sa;
 }
+#endif
 
 socklen_t
 IPEndpoint::size() const {
@@ -302,6 +291,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 +358,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;
@@ -424,6 +453,37 @@ IPAddr::load(const std::string_view&text) {
   return this->is_valid();
 }
 
+IPAddr&
+IPAddr::assign(sockaddr const *addr) {
+  if (addr) {
+    switch (addr->sa_family) {
+      case AF_INET:return this->assign(reinterpret_cast<sockaddr_in const 
*>(addr));
+      case AF_INET6:return this->assign(reinterpret_cast<sockaddr_in6 const 
*>(addr));
+      default:break;
+    }
+  }
+  _family = AF_UNSPEC;
+  return *this;
+}
+
+IPAddr::self_type& IPAddr::operator&=(IPMask const& mask) {
+  if (_family == AF_INET) {
+    _addr._ip4 &= mask;
+  } else if (_family == AF_INET6) {
+    _addr._ip6 &= mask;
+  }
+  return *this;
+}
+
+IPAddr::self_type& IPAddr::operator|=(IPMask const& mask) {
+  if (_family == AF_INET) {
+    _addr._ip4 |= mask;
+  } else if (_family == AF_INET6) {
+    _addr._ip6 |= mask;
+  }
+  return *this;
+}
+
 #if 0
 bool
 operator==(IPAddr const &lhs, sockaddr const *rhs)
@@ -512,14 +572,132 @@ 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 };
+}
+
+// ++ IPNet ++
+
+bool IP4Net::load(TextView text) {
+  auto idx = text.find('/');
+  if (idx != text.npos) {
+    if (idx + 1 < text.size()) { // must have something past the separator or 
it's bogus.
+      IP4Addr addr;
+      if (addr.load(text.substr(0, idx))) { // load the address
+        IPMask mask;
+        text.remove_prefix(idx + 1); // drop address and separator.
+        if (mask.load(text)) {
+          this->assign(addr, mask);
+          return true;
+        }
+      }
+    }
+  }
+
+  this->clear();
+  return false;
+}
+
+bool IP6Net::load(TextView text) {
+  auto idx = text.find('/');
+  if (idx != text.npos) {
+    if (idx + 1 < text.size()) { // must have something past the separator or 
it's bogus.
+      IP6Addr addr;
+      if (addr.load(text.substr(0, idx))) { // load the address
+        IPMask mask;
+        text.remove_prefix(idx + 1); // drop address and separator.
+        if (mask.load(text)) {
+          this->assign(addr, mask);
+          return true;
+        }
+      }
+    }
+  }
+
+  this->clear();
+  return false;
+}
+
+bool IPNet::load(TextView text) {
+  auto mask_text = text.split_suffix_at('/');
+  if (!mask_text.empty()) {
+    IPMask mask;
+    if (mask.load(mask_text)) {
+      if (IP6Addr addr; addr.load(text)) { // load the address
+        this->assign(addr, mask);
+        return true;
+      } else if (IP4Addr addr; addr.load(text)) {
+        this->assign(addr, mask);
+        return true;
+      }
+    }
+  }
+  this->clear();
+  return false;
+}
+
+// +++ IP4Range +++
+
 IP4Range::IP4Range(swoc::IP4Addr const&addr, swoc::IPMask const&mask) {
   this->assign(addr, mask);
 }
@@ -568,6 +746,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 +798,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;
@@ -622,6 +844,16 @@ bool IP6Range::load(std::string_view text) {
   return false;
 }
 
+IPRange::IPRange(IPAddr const& min, IPAddr const& max) {
+  if (min.is_ip4() && max.is_ip4()) {
+    _range._ip4.assign(min.ip4(), max.ip4());
+    _family = AF_INET;
+  } else if (min.is_ip6() && max.is_ip6()) {
+    _range._ip6.assign(min.ip6(), max.ip6());
+    _family = AF_INET6;
+  }
+}
+
 bool IPRange::load(std::string_view const&text) {
   static const string_view CHARS{".:"};
   auto idx = text.find_first_of(CHARS);
@@ -668,4 +900,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/swoc++/swoc++-headers.part b/swoc++/swoc++-headers.part
new file mode 100644
index 0000000..4c61a89
--- /dev/null
+++ b/swoc++/swoc++-headers.part
@@ -0,0 +1,6 @@
+Import("*")
+PartName("headers")
+
+# export the include directory
+inc_files = Pattern(src_dir="include", includes=["swoc/*.h"])
+env.InstallInclude(inc_files)
diff --git a/swoc++/swoc++-shared.part b/swoc++/swoc++-shared.part
index df0b7d3..7d52392 100644
--- a/swoc++/swoc++-shared.part
+++ b/swoc++/swoc++-shared.part
@@ -1,6 +1,8 @@
 Import("*")
 PartName("shared")
 
+DependsOn([ Component("libswoc.headers") ])
+
 src_files = env.get("src_files")
 
 env.AppendUnique(
diff --git a/swoc++/swoc++-static.part b/swoc++/swoc++-static.part
index e7defe3..02b1759 100644
--- a/swoc++/swoc++-static.part
+++ b/swoc++/swoc++-static.part
@@ -1,6 +1,8 @@
 Import("*")
 PartName("static")
 
+DependsOn([ Component("libswoc.headers") ])
+
 src_files = env.get("src_files")
 
 env.AppendUnique(
diff --git a/swoc++/swoc++.part b/swoc++/swoc++.part
index 4c4df61..83c9d5d 100644
--- a/swoc++/swoc++.part
+++ b/swoc++/swoc++.part
@@ -14,15 +14,6 @@ src_files = [
     "src/TextView.cc",
 ]
 
-# export the include directory
-inc_files = Pattern(src_dir="include/swoc", includes=["*.h"])
-#env.InstallInclude(env.get("inc_files"))
-env.InstallInclude(inc_files)
-
-env.AppendUnique(
-    CCFLAGS=['-std=c++17' ],
-    CPPPATH=["include"],
-)
-
-env.Part("swoc++-static.part", package_group="libswoc", src_files=src_files, 
inc_files=inc_files)
-env.Part("swoc++-shared.part", package_group="libswoc", src_files=src_files, 
inc_files=inc_files)
+env.Part("swoc++-static.part", package_group="libswoc", src_files=src_files)
+env.Part("swoc++-shared.part", package_group="libswoc", src_files=src_files)
+env.Part("swoc++-headers.part", package_group="libswoc")
diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt
index fd17d9f..39de7a7 100644
--- a/unit_tests/CMakeLists.txt
+++ b/unit_tests/CMakeLists.txt
@@ -28,4 +28,4 @@ add_executable(test_libswoc
 
 target_link_libraries(test_libswoc PUBLIC swoc++)
 set_target_properties(test_libswoc PROPERTIES CLANG_FORMAT_DIRS 
${CMAKE_CURRENT_SOURCE_DIR})
-add_definitions(-DVERBOSE_EXAMPLE_OUTPUT=1)
+#add_definitions(-DVERBOSE_EXAMPLE_OUTPUT=1)
diff --git a/unit_tests/ex_ipspace_properties.cc 
b/unit_tests/ex_ipspace_properties.cc
index e0f0e8e..3420e6a 100644
--- a/unit_tests/ex_ipspace_properties.cc
+++ b/unit_tests/ex_ipspace_properties.cc
@@ -348,7 +348,8 @@ TextView Table::localize(TextView const&src) {
 TextView Table::token(TextView & line) {
   TextView::size_type idx = 0;
   // Characters of interest.
-  TextView sep_list { {'"', SEP} , 2 };
+  static char constexpr separators[2] = { '"', SEP };
+  static TextView sep_list { separators, 2 };
   bool in_quote_p  = false;
   while (idx < line.size()) {
     // Next character of interest.
diff --git a/unit_tests/test_ip.cc b/unit_tests/test_ip.cc
index 9d97cc2..a088e72 100644
--- a/unit_tests/test_ip.cc
+++ b/unit_tests/test_ip.cc
@@ -13,6 +13,7 @@
 #include <swoc/TextView.h>
 #include <swoc/swoc_ip.h>
 #include <swoc/bwf_ip.h>
+#include <swoc/bwf_std.h>
 #include <swoc/swoc_file.h>
 
 using namespace std::literals;
@@ -28,6 +29,9 @@ using swoc::IP6Range;
 
 using swoc::IPAddr;
 using swoc::IPRange;
+
+using swoc::IPMask;
+
 using W = swoc::LocalBufferWriter<256>;
 
 TEST_CASE("Basic IP", "[libswoc][ip]") {
@@ -63,7 +67,7 @@ TEST_CASE("Basic IP", "[libswoc][ip]") {
     REQUIRE(s.rest == rest);
   }
 
-  IP4Addr alpha { "172.96.12.134"};
+  IP4Addr alpha{"172.96.12.134"};
   CHECK(alpha == IP4Addr{"172.96.12.134"});
   CHECK(alpha == IP4Addr{IPAddr{"172.96.12.134"}});
   CHECK(alpha == IPAddr{IPEndpoint{"172.96.12.134:80"}});
@@ -269,6 +273,257 @@ 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}));
+
+  std::array<swoc::IP4Net, 7> r_4_nets =
+      {{
+           "10.33.45.19/32"_tv
+           , "10.33.45.20/30"_tv
+           , "10.33.45.24/29"_tv
+           , "10.33.45.32/27"_tv
+           , "10.33.45.64/29"_tv
+           , "10.33.45.72/30"_tv
+           , "10.33.45.76/32"_tv
+       }};
+  auto r4_net = r_4_nets.begin();
+  for (auto const &net : r_4.networks()) {
+    REQUIRE(r4_net != r_4_nets.end());
+    CHECK(*r4_net == net);
+    ++r4_net;
+  }
+
+  // Let's try that again, with @c IPRange instead.
+  r4_net = r_4_nets.begin();
+  for (auto const& net : IPRange{r_4}.networks()) {
+    REQUIRE(r4_net != r_4_nets.end());
+    CHECK(*r4_net == net);
+    ++r4_net;
+  }
+
+  std::array<swoc::IP6Net, 130> r_5_nets =
+      {{
+           {IP6Addr{
+               "2001:1f2d:c587:24c3:9128:3349:3cee:143"}, IPMask{
+               128}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:144"}, IPMask{126}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:148"}, IPMask{125}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:150"}, IPMask{124}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:160"}, IPMask{123}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:180"}, IPMask{121}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:200"}, IPMask{119}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:400"}, IPMask{118}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:800"}, IPMask{117}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:1000"}, IPMask{116}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:2000"}, IPMask{115}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:4000"}, IPMask{114}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cee:8000"}, IPMask{113}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cef:0"}, IPMask{112}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3cf0:0"}, IPMask{108}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3d00:0"}, IPMask{104}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:3e00:0"}, IPMask{103}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:4000:0"}, IPMask{98}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3349:8000:0"}, IPMask{97}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:334a::"}, IPMask{95}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:334c::"}, IPMask{94}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3350::"}, IPMask{92}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3360::"}, IPMask{91}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3380::"}, IPMask{89}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3400::"}, IPMask{86}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:3800::"}, IPMask{85}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:4000::"}, IPMask{82}}
+           , {IP6Addr{
+              "2001:1f2d:c587:24c3:9128:8000::"}, IPMask{81}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9129::"}, IPMask{
+              80}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:912a::"}, IPMask{
+              79}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:912c::"}, IPMask{
+              78}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9130::"}, IPMask{
+              76}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9140::"}, IPMask{
+              74}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9180::"}, IPMask{
+              73}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9200::"}, IPMask{
+              71}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9400::"}, IPMask{
+              70}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:9800::"}, IPMask{
+              69}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:a000::"}, IPMask{
+              67}}
+           , {IP6Addr{"2001:1f2d:c587:24c3:c000::"}, IPMask{
+              66}}
+           , {IP6Addr{"2001:1f2d:c587:24c4::"}, IPMask{62}}
+           , {IP6Addr{"2001:1f2d:c587:24c8::"}, IPMask{61}}
+           , {IP6Addr{"2001:1f2d:c587:24d0::"}, IPMask{60}}
+           , {IP6Addr{"2001:1f2d:c587:24e0::"}, IPMask{59}}
+           , {IP6Addr{"2001:1f2d:c587:2500::"}, IPMask{56}}
+           , {IP6Addr{"2001:1f2d:c587:2600::"}, IPMask{55}}
+           , {IP6Addr{"2001:1f2d:c587:2800::"}, IPMask{53}}
+           , {IP6Addr{"2001:1f2d:c587:3000::"}, IPMask{52}}
+           , {IP6Addr{"2001:1f2d:c587:4000::"}, IPMask{50}}
+           , {IP6Addr{"2001:1f2d:c587:8000::"}, IPMask{49}}
+           , {IP6Addr{"2001:1f2d:c588::"}, IPMask{45}}
+           , {IP6Addr{"2001:1f2d:c590::"}, IPMask{44}}
+           , {IP6Addr{"2001:1f2d:c5a0::"}, IPMask{43}}
+           , {IP6Addr{"2001:1f2d:c5c0::"}, IPMask{42}}
+           , {IP6Addr{"2001:1f2d:c600::"}, IPMask{39}}
+           , {IP6Addr{"2001:1f2d:c800::"}, IPMask{37}}
+           , {IP6Addr{"2001:1f2d:d000::"}, IPMask{36}}
+           , {IP6Addr{"2001:1f2d:e000::"}, IPMask{35}}
+           , {IP6Addr{"2001:1f2e::"}, IPMask{31}}
+           , {IP6Addr{"2001:1f30::"}, IPMask{28}}
+           , {IP6Addr{"2001:1f40::"}, IPMask{26}}
+           , {IP6Addr{"2001:1f80::"}, IPMask{25}}
+           , {IP6Addr{"2001:2000::"}, IPMask{19}}
+           , {IP6Addr{"2001:4000::"}, IPMask{18}}
+           , {IP6Addr{"2001:8000::"}, IPMask{17}}
+           , {IP6Addr{"2002::"}, IPMask{15}}
+           , {IP6Addr{"2004::"}, IPMask{14}}
+           , {IP6Addr{"2008::"}, IPMask{13}}
+           , {IP6Addr{"2010::"}, IPMask{12}}
+           , {IP6Addr{"2020::"}, IPMask{11}}
+           , {IP6Addr{"2040::"}, IPMask{10}}
+           , {IP6Addr{"2080::"}, IPMask{9}}
+           , {IP6Addr{"2100::"}, IPMask{8}}
+           , {IP6Addr{"2200::"}, IPMask{7}}
+           , {IP6Addr{"2400::"}, IPMask{6}}
+           , {IP6Addr{"2800::"}, IPMask{5}}
+           , {IP6Addr{"3000::"}, IPMask{4}}
+           , {IP6Addr{"4000::"}, IPMask{2}}
+           , {IP6Addr{"8000::"}, IPMask{2}}
+           , {IP6Addr{"c000::"}, IPMask{3}}
+           , {IP6Addr{"e000::"}, IPMask{4}}
+           , {IP6Addr{"f000::"}, IPMask{5}}
+           , {IP6Addr{"f800::"}, IPMask{6}}
+           , {IP6Addr{"fc00::"}, IPMask{7}}
+           , {IP6Addr{"fe00::"}, IPMask{8}}
+           , {IP6Addr{"ff00::"}, IPMask{9}}
+           , {IP6Addr{"ff80::"}, IPMask{10}}
+           , {IP6Addr{"ffc0::"}, IPMask{11}}
+           , {IP6Addr{"ffe0::"}, IPMask{13}}
+           , {IP6Addr{"ffe8::"}, IPMask{14}}
+           , {IP6Addr{"ffec::"}, IPMask{15}}
+           , {IP6Addr{"ffee::"}, IPMask{20}}
+           , {IP6Addr{"ffee:1000::"}, IPMask{21}}
+           , {IP6Addr{"ffee:1800::"}, IPMask{22}}
+           , {IP6Addr{"ffee:1c00::"}, IPMask{23}}
+           , {IP6Addr{"ffee:1e00::"}, IPMask{24}}
+           , {IP6Addr{"ffee:1f00::"}, IPMask{27}}
+           , {IP6Addr{"ffee:1f20::"}, IPMask{29}}
+           , {IP6Addr{"ffee:1f28::"}, IPMask{30}}
+           , {IP6Addr{"ffee:1f2c::"}, IPMask{32}}
+           , {IP6Addr{"ffee:1f2d::"}, IPMask{33}}
+           , {IP6Addr{"ffee:1f2d:8000::"}, IPMask{34}}
+           , {IP6Addr{"ffee:1f2d:c000::"}, IPMask{38}}
+           , {IP6Addr{"ffee:1f2d:c400::"}, IPMask{40}}
+           , {IP6Addr{"ffee:1f2d:c500::"}, IPMask{41}}
+           , {IP6Addr{"ffee:1f2d:c580::"}, IPMask{46}}
+           , {IP6Addr{"ffee:1f2d:c584::"}, IPMask{47}}
+           , {IP6Addr{"ffee:1f2d:c586::"}, IPMask{48}}
+           , {IP6Addr{"ffee:1f2d:c587::"}, IPMask{51}}
+           , {IP6Addr{"ffee:1f2d:c587:2000::"}, IPMask{54}}
+           , {IP6Addr{"ffee:1f2d:c587:2400::"}, IPMask{57}}
+           , {IP6Addr{"ffee:1f2d:c587:2480::"}, IPMask{58}}
+           , {IP6Addr{"ffee:1f2d:c587:24c0::"}, IPMask{63}}
+           , {IP6Addr{"ffee:1f2d:c587:24c2::"}, IPMask{64}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3::"}, IPMask{65}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3:8000::"}, IPMask{
+              68}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3:9000::"}, IPMask{
+              72}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3:9100::"}, IPMask{
+              75}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3:9120::"}, IPMask{
+              77}}
+           , {IP6Addr{"ffee:1f2d:c587:24c3:9128::"}, IPMask{
+              83}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:2000::"}, IPMask{84}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3000::"}, IPMask{87}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3200::"}, IPMask{88}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3300::"}, IPMask{90}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3340::"}, IPMask{93}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3348::"}, IPMask{96}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3349::"}, IPMask{99}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3349:2000:0"}, IPMask{100}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3349:3000:0"}, IPMask{101}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3349:3800:0"}, IPMask{102}}
+           , {IP6Addr{
+              "ffee:1f2d:c587:24c3:9128:3349:3c00:0"}, IPMask{104}}
+       }};
+
+  auto r5_net = r_5_nets.begin();
+  for (auto const&[addr, mask] : r_5.networks()) {
+    REQUIRE(r5_net != r_5_nets.end());
+    CHECK(*r5_net == swoc::IP6Net{addr, mask});
+    ++r5_net;
+  }
+
+  // Try it again, using @c IPRange.
+  r5_net = r_5_nets.begin();
+  for ( auto const&[addr, mask] : IPRange{r_5}.networks()) {
+    REQUIRE(r5_net != r_5_nets.end());
+    CHECK(*r5_net == swoc::IPNet{addr, mask});
+    ++r5_net;
+  }
+}
+
 TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") {
   using int_space = swoc::IPSpace<unsigned>;
   int_space space;
@@ -336,20 +591,20 @@ TEST_CASE("IP Space Int", "[libswoc][ip][ipspace]") {
   REQUIRE(payload != nullptr);
   REQUIRE(*payload == 0x5);
 
-  space.blend({r_2.min(), r_3.max()}, 0x6, BF);
+  space.blend(IPRange{r_2.min(), r_3.max()}, 0x6, BF);
   REQUIRE(space.count() == 4);
 
   std::array<std::tuple<TextView, int>, 9> ranges = {
       {
-          { "100.0.0.0-100.0.0.255",  0 }
-          , { "100.0.1.0-100.0.1.255",  1 }
-          , { "100.0.2.0-100.0.2.255",  2 }
-          , { "100.0.3.0-100.0.3.255",  3 }
-          , { "100.0.4.0-100.0.4.255",  4 }
-          , { "100.0.5.0-100.0.5.255",  5 }
-          , { "100.0.6.0-100.0.6.255",  6 }
-          , { "100.0.0.0-100.0.0.255",  31 }
-          , { "100.0.1.0-100.0.1.255",  30 }
+          {"100.0.0.0-100.0.0.255", 0}
+          , {"100.0.1.0-100.0.1.255", 1}
+          , {"100.0.2.0-100.0.2.255", 2}
+          , {"100.0.3.0-100.0.3.255", 3}
+          , {"100.0.4.0-100.0.4.255", 4}
+          , {"100.0.5.0-100.0.5.255", 5}
+          , {"100.0.6.0-100.0.6.255", 6}
+          , {"100.0.0.0-100.0.0.255", 31}
+          , {"100.0.1.0-100.0.1.255", 30}
       }};
 
   space.clear();
@@ -393,7 +648,7 @@ TEST_CASE("IPSpace bitset", "[libswoc][ipspace][bitset]") {
 TEST_CASE("IPSpace docJJ", "[libswoc][ipspace][docJJ]") {
   using PAYLOAD = std::bitset<32>;
   using Space = swoc::IPSpace<PAYLOAD>;
-  auto blender = [](PAYLOAD& lhs, PAYLOAD const& rhs) -> bool {
+  auto blender = [](PAYLOAD&lhs, PAYLOAD const&rhs) -> bool {
     lhs |= rhs;
     return true;
   };
@@ -405,33 +660,34 @@ TEST_CASE("IPSpace docJJ", "[libswoc][ipspace][docJJ]") {
     return bits;
   };
 
-  std::array<std::tuple<TextView, std::initializer_list<unsigned>>, 9> ranges 
= {
+  std::array<std::tuple<TextView, PAYLOAD>, 9> ranges = {
       {
-            { "100.0.0.0-100.0.0.255", { 0 } }
-          , { "100.0.1.0-100.0.1.255", { 1 } }
-          , { "100.0.2.0-100.0.2.255", { 2 } }
-          , { "100.0.3.0-100.0.3.255", { 3 } }
-          , { "100.0.4.0-100.0.4.255", { 4 } }
-          , { "100.0.5.0-100.0.5.255", { 5 } }
-          , { "100.0.6.0-100.0.6.255", { 6 } }
-          , { "100.0.0.0-100.0.0.255", { 31 } }
-          , { "100.0.1.0-100.0.1.255", { 30 } }
+          {"100.0.0.0-100.0.0.255", make_bits({0})}
+          , {"100.0.1.0-100.0.1.255", make_bits({1})}
+          , {"100.0.2.0-100.0.2.255", make_bits({2})}
+          , {"100.0.3.0-100.0.3.255", make_bits({3})}
+          , {"100.0.4.0-100.0.4.255", make_bits({4})}
+          , {"100.0.5.0-100.0.5.255", make_bits({5})}
+          , {"100.0.6.0-100.0.6.255", make_bits({6})}
+          , {"100.0.0.0-100.0.0.255", make_bits({31})}
+          , {"100.0.1.0-100.0.1.255", make_bits({30})}
       }};
 
   std::array<std::initializer_list<unsigned>, 7> results = {{
-        { 0, 31 }
-      , { 1, 30 }
-      , { 2 }
-      , { 3 }
-      , { 4 }
-      , { 5 }
-      , { 6 }
-  }};
+                                                                {0, 31}
+                                                                , {1, 30}
+                                                                , {2}
+                                                                , {3}
+                                                                , {4}
+                                                                , {5}
+                                                                , {6}
+                                                            }};
 
   Space space;
 
-  for (auto &&[text, bit_list] : ranges) {
-    space.blend(IPRange{text}, make_bits(bit_list), blender);
+  for (auto && [text, bit_list] : ranges) {
+    std::cout << W().print("{} = {}\n", text, bit_list);
+    space.blend(IPRange{text}, bit_list, blender);
   }
 
   // Check iteration - verify forward and reverse iteration yield the correct 
number of ranges
@@ -440,19 +696,32 @@ TEST_CASE("IPSpace docJJ", "[libswoc][ipspace][docJJ]") {
   unsigned idx;
 
   idx = 0;
-  for ( auto const& [ range, bits ] : space) {
+  for (auto const&[range, bits] : space) {
     REQUIRE(idx < results.size());
     CHECK(bits == make_bits(results[idx]));
     ++idx;
   }
 
   idx = results.size();
-  for ( auto spot = space.end() ; spot != space.begin() ; ) {
-    auto const& [ range, bits ] { *--spot };
+  for (auto spot = space.end(); spot != space.begin();) {
+    auto const&[range, bits]{*--spot};
     REQUIRE(idx > 0);
     --idx;
     CHECK(bits == make_bits(results[idx]));
   }
+
+  // Check iterator copying.
+  idx = 0;
+  Space::iterator iter;
+  IPRange range;
+  PAYLOAD bits;
+  for (auto spot = space.begin(); spot != space.end() ; ++spot) {
+    iter = spot;
+    std::tie(range, bits) = *iter;
+    REQUIRE(idx < results.size());
+    CHECK(bits == make_bits(results[idx]));
+    ++idx;
+  }
 }
 
 #if 0

Reply via email to