blob: fe1f66691a1ed2a390b45b709a06380cb9cb6ede [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright Network Geographics 2014
/** @file
IP address support.
*/
#include "swoc/swoc_ip.h"
#include "swoc/swoc_meta.h"
using swoc::TextView;
using swoc::svtoi;
using swoc::svtou;
using namespace swoc::literals;
namespace {
// Handle the @c sin_len member, the presence of which varies across compilation environments.
template<typename T>
auto
Set_Sockaddr_Len_Case(T *, swoc::meta::CaseTag<0>) -> decltype(swoc::meta::TypeFunc<void>()) {
}
template<typename T>
auto
Set_Sockaddr_Len_Case(T *addr
, swoc::meta::CaseTag<1>) -> decltype(T::sin_len, swoc::meta::TypeFunc<void>()) {
addr->sin_len = sizeof(T);
}
template<typename T>
void
Set_Sockaddr_Len(T *addr) {
Set_Sockaddr_Len_Case(addr, swoc::meta::CaseArg);
}
} // namespace
namespace swoc { inline namespace SWOC_VERSION_NS {
IPAddr const IPAddr::INVALID;
IP4Addr const IP4Addr::MIN{INADDR_ANY};
IP4Addr const IP4Addr::MAX{INADDR_BROADCAST};
IP6Addr const IP6Addr::MIN{0, 0};
IP6Addr const IP6Addr::MAX{std::numeric_limits<uint64_t>::max()
, std::numeric_limits<uint64_t>::max()};
// The Intel compiler won't let me directly use @c nullptr to construct a reference, so I need
// to fake it out by using this psuedo-nullptr.
void * const pseudo_nullptr = 0;
bool
IPEndpoint::assign(sockaddr *dst, sockaddr const *src) {
size_t n = 0;
if (dst != src) {
self_type::invalidate(dst);
switch (src->sa_family) {
case AF_INET:n = sizeof(sockaddr_in);
break;
case AF_INET6:n = sizeof(sockaddr_in6);
break;
}
if (n) {
memcpy(dst, src, n);
}
}
return n != 0;
}
IPEndpoint&
IPEndpoint::assign(IPAddr const& src, in_port_t port) {
switch (src.family()) {
case AF_INET: {
memset(&sa4, 0, sizeof sa4);
sa4.sin_family = AF_INET;
sa4.sin_addr.s_addr = src.ip4().network_order();
sa4.sin_port = port;
Set_Sockaddr_Len(&sa4);
}
break;
case AF_INET6: {
memset(&sa6, 0, sizeof sa6);
sa6.sin6_family = AF_INET6;
sa6.sin6_addr = src.ip6().network_order();
sa6.sin6_port = port;
Set_Sockaddr_Len(&sa6);
}
break;
}
return *this;
}
bool
IPEndpoint::tokenize(std::string_view str, std::string_view *addr, std::string_view *port
, std::string_view *rest) {
TextView src(str); /// Easier to work with for parsing.
// In case the incoming arguments are null, set them here and only check for null once.
// it doesn't matter if it's all the same, the results will be thrown away.
std::string_view local;
if (!addr) {
addr = &local;
}
if (!port) {
port = &local;
}
if (!rest) {
rest = &local;
}
*addr = std::string_view{};
*port = std::string_view{};
*rest = std::string_view{};
// Let's see if we can find out what's in the address string.
if (src) {
bool colon_p = false;
src.ltrim_if(&isspace);
// Check for brackets.
if ('[' == *src) {
++src; // skip bracket.
*addr = src.take_prefix_at(']');
if (':' == *src) {
colon_p = true;
++src;
}
} else {
TextView::size_type last = src.rfind(':');
if (last != TextView::npos && last == src.find(':')) {
// Exactly one colon - leave post colon stuff in @a src.
*addr = src.take_prefix(last);
colon_p = true;
} else { // presume no port, use everything.
*addr = src;
src.clear();
}
}
if (colon_p) {
TextView tmp{src};
src.ltrim_if(&isdigit);
if (tmp.data() == src.data()) { // no digits at all
src.assign(tmp.data() - 1, tmp.size() + 1); // back up to include colon
} else {
*port = std::string_view(tmp.data(), src.data() - tmp.data());
}
}
*rest = src;
}
return !addr->empty(); // true if we found an address.
}
bool
IPEndpoint::parse(std::string_view const& str) {
TextView addr_str, port_str, rest;
TextView src{TextView{str}.trim_if(&isspace)};
if (this->tokenize(src, &addr_str, &port_str, &rest)) {
if (rest.empty()) {
if (IPAddr addr ; addr.load(addr_str)) {
in_port_t port = 0;
if (!port_str.empty()) {
uintmax_t n{swoc::svto_radix<10>(port_str)};
if (!port_str.empty() || n > std::numeric_limits<in_port_t>::max()) {
return false;
}
port = n;
}
this->assign(addr, htons(port));
return true;
}
}
}
return false;
}
socklen_t
IPEndpoint::size() const {
switch (sa.sa_family) {
case AF_INET:return sizeof(sockaddr_in);
case AF_INET6:return sizeof(sockaddr_in6);
default:return sizeof(sockaddr);
}
}
std::string_view
IPEndpoint::family_name(sa_family_t family) {
switch (family) {
case AF_INET:return "ipv4"_sv;
case AF_INET6:return "ipv6"_sv;
case AF_UNIX:return "unix"_sv;
case AF_UNSPEC:return "unspec"_sv;
}
return "unknown"_sv;
}
IPEndpoint&
IPEndpoint::set_to_any(int family) {
memset(this, 0, sizeof(*this));
if (AF_INET == family) {
sa4.sin_family = family;
sa4.sin_addr.s_addr = INADDR_ANY;
Set_Sockaddr_Len(&sa4);
} else if (AF_INET6 == family) {
sa6.sin6_family = family;
sa6.sin6_addr = in6addr_any;
Set_Sockaddr_Len(&sa6);
}
return *this;
}
bool
IPEndpoint::is_any() const {
bool zret = false;
switch (this->family()) {
case AF_INET: zret = sa4.sin_addr.s_addr == INADDR_ANY; break;
case AF_INET6: zret = IN6_IS_ADDR_UNSPECIFIED(&sa6.sin6_addr); break;
}
return zret;
}
IPEndpoint&
IPEndpoint::set_to_loopback(int family) {
memset(this, 0, sizeof(*this));
if (AF_INET == family) {
sa.sa_family = family;
sa4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
Set_Sockaddr_Len(&sa4);
} else if (AF_INET6 == family) {
sa.sa_family = family;
sa6.sin6_addr = in6addr_loopback;
Set_Sockaddr_Len(&sa6);
}
return *this;
}
bool
IPEndpoint::is_loopback() const {
bool zret = false;
switch (this->family()) {
case AF_INET: zret = ((ntohl(sa4.sin_addr.s_addr) & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET; break;
case AF_INET6: zret = IN6_IS_ADDR_LOOPBACK(&sa6.sin6_addr); break;
}
return zret;
}
bool
IP4Addr::load(std::string_view const& text) {
TextView src{text};
int n = SIZE; /// # of octets
if (src.empty() || ('[' == *src && ((++src).empty() || src.back() != ']'))) {
return false;
}
auto octet = reinterpret_cast<uint8_t *>(&_addr);
while (n > 0 && !src.empty()) {
TextView token{src.take_prefix_at('.')};
auto x = svto_radix<10>(token);
if (token.empty() && x <= std::numeric_limits<uint8_t>::max()) {
octet[--n] = x;
} else {
break;
}
}
if (n == 0 && src.empty()) {
return true;
}
_addr = INADDR_ANY;
return false;
}
IP4Addr::IP4Addr(sockaddr_in const *sa) : _addr(reorder(sa->sin_addr.s_addr)) {}
auto IP4Addr::operator=(sockaddr_in const *sa) -> self_type& {
_addr = reorder(sa->sin_addr.s_addr);
return *this;
}
sockaddr_in *IP4Addr::fill(sockaddr_in *sa, in_port_t port) const {
sa->sin_addr.s_addr = this->network_order();
sa->sin_port = port;
return sa;
}
int IP6Addr::cmp(const self_type& that) const {
return *this < that ? -1 : *this > that ? 1 : 0;
}
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};
int n = 0;
int empty_idx = -1;
auto quad = _addr._quad.data();
if (src && '[' == *src) {
++src;
if (src.empty() || src.back() != ']') {
return false;
}
src.remove_suffix(1);
}
if (src.size() < 2) {
return false;
}
// If the first character is ':' then it is an error for it not to be followed by another ':'.
// Special case the empty address and loopback, otherwise just make a note of the leading '::'.
if (src[0] == ':') {
if (src[1] == ':') {
if (src.size() == 2) {
this->clear();
return true;
} else if (src.size() == 3 && src[2] == '1') {
_addr._store[0] = 0;
_addr._store[1] = 1;
return true;
} else {
empty_idx = n;
src.remove_prefix(2);
}
} else {
return false;
}
}
// Sadly the empty quads can't be done in line because it's not possible to know the correct index
// of the next present quad until the entire address has been parsed.
while (n < static_cast<int>(N_QUADS) && !src.empty()) {
TextView token{src.take_prefix_at(':')};
if (token.empty()) {
if (empty_idx >= 0) { // two instances of "::", fail.
return false;
}
empty_idx = n;
} else {
TextView r;
auto x = svtoi(token, &r, 16);
if (r.size() == token.size()) {
quad[QUAD_IDX[n++]] = x;
} else {
break;
}
}
}
// Handle empty quads - invalid if empty and still had a full set of quads
if (empty_idx >= 0) {
if (n < static_cast<int>(N_QUADS)) {
int nil_idx = N_QUADS - (n - empty_idx);
int delta = N_QUADS - n;
for (int k = N_QUADS - 1; k >= empty_idx; --k) {
quad[QUAD_IDX[k]] = (k >= nil_idx ? quad[QUAD_IDX[k - delta]] : 0);
}
n = N_QUADS; // Mark success.
} else {
return false; // too many quads - full set plus empty.
}
}
if (n == N_QUADS && src.empty()) {
return true;
}
this->clear();
return false;
}
// These are Intel correct, at some point will need to be architecture dependent.
void IP6Addr::reorder(in6_addr& dst, raw_type const& src) {
self_type::reorder(dst.s6_addr, src.data());
self_type::reorder(dst.s6_addr + WORD_SIZE, src.data() + WORD_SIZE);
}
void IP6Addr::reorder(raw_type& dst, in6_addr const& src) {
self_type::reorder(dst.data(), src.s6_addr);
self_type::reorder(dst.data() + WORD_SIZE, src.s6_addr + WORD_SIZE);
}
bool
IPAddr::load(const std::string_view& text) {
TextView src{text};
src.ltrim_if(&isspace);
if (TextView::npos != src.prefix(5).find_first_of('.')) {
_family = AF_INET;
} else if (TextView::npos != src.prefix(6).find_first_of(':')) {
_family = AF_INET6;
} else {
_family = AF_UNSPEC;
}
// Do the real parse now
switch (_family) {
case AF_INET:
if (!_addr._ip4.load(src)) {
_family = AF_UNSPEC;
}
break;
case AF_INET6:
if (!_addr._ip6.load(src)) {
_family = AF_UNSPEC;
}
break;
}
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;
}
bool IPAddr::operator<(self_type const& that) const {
if (AF_INET == _family) {
switch (that._family) {
case AF_INET: return _addr._ip4 < that._addr._ip4;
case AF_INET6: return false;
default: return true;
}
} else if (AF_INET6 == _family) {
switch (that._family) {
case AF_INET: return true;
case AF_INET6: return _addr._ip6 < that._addr._ip6;
default: return true;
}
}
return false;
}
int IPAddr::cmp(self_type const& that) const {
if (AF_INET == _family) {
switch (that._family) {
case AF_INET: return _addr._ip4.cmp(that._addr._ip4);
case AF_INET6: return -1;
default: return 1;
}
} else if (AF_INET6 == _family) {
switch (that._family) {
case AF_INET: return 1;
case AF_INET6: return _addr._ip6.cmp(that._addr._ip6);
default: return 1;
}
}
return 0;
}
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)
{
bool zret = false;
if (lhs._family == rhs->sa_family) {
if (AF_INET == lhs._family) {
zret = lhs._addr._ip4 == ats_ip4_addr_cast(rhs);
} else if (AF_INET6 == lhs._family) {
zret = 0 == memcmp(&lhs._addr._ip6, &ats_ip6_addr_cast(rhs), sizeof(in6_addr));
} else { // map all non-IP to the same thing.
zret = true;
}
} // else different families, not equal.
return zret;
}
/** Compare two IP addresses.
This is useful for IPv4, IPv6, and the unspecified address type.
If the addresses are of different types they are ordered
Non-IP < IPv4 < IPv6
- all non-IP addresses are the same ( including @c AF_UNSPEC )
- IPv4 addresses are compared numerically (host order)
- IPv6 addresses are compared byte wise in network order (MSB to LSB)
@return
- -1 if @a lhs is less than @a rhs.
- 0 if @a lhs is identical to @a rhs.
- 1 if @a lhs is greater than @a rhs.
*/
int
IPAddr::cmp(self_type const &that) const
{
int zret = 0;
uint16_t rtype = that._family;
uint16_t ltype = _family;
// We lump all non-IP addresses into a single equivalence class
// that is less than an IP address. This includes AF_UNSPEC.
if (AF_INET == ltype) {
if (AF_INET == rtype) {
in_addr_t la = ntohl(_addr._ip4);
in_addr_t ra = ntohl(that._addr._ip4);
if (la < ra) {
zret = -1;
} else if (la > ra) {
zret = 1;
} else {
zret = 0;
}
} else if (AF_INET6 == rtype) { // IPv4 < IPv6
zret = -1;
} else { // IP > not IP
zret = 1;
}
} else if (AF_INET6 == ltype) {
if (AF_INET6 == rtype) {
zret = memcmp(&_addr._ip6, &that._addr._ip6, TS_IP6_SIZE);
} else {
zret = 1; // IPv6 greater than any other type.
}
} else if (AF_INET == rtype || AF_INET6 == rtype) {
// ltype is non-IP so it's less than either IP type.
zret = -1;
} else { // Both types are non-IP so they're equal.
zret = 0;
}
return zret;
}
#endif
bool
IPAddr::is_multicast() const {
return (AF_INET == _family && 0xe == (_addr._octet[0] >> 4)) ||
(AF_INET6 == _family && _addr._ip6.is_multicast());
}
IPEndpoint::IPEndpoint(string_view const& text) {
this->invalidate();
this->parse(text);
}
bool IPMask::load(string_view const& text) {
TextView parsed;
_cidr = swoc::svtou(text, &parsed);
if (parsed.size() != text.size()) {
_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);
}
IP4Range& IP4Range::assign(swoc::IP4Addr const& addr, swoc::IPMask const& mask) {
// Special case the cidr sizes for 0, maximum
if (0 == mask.width()) {
_min = metric_type::MIN;
_max = metric_type::MAX;
} else {
_min = _max = addr;
if (mask.width() < 32) {
in_addr_t bits = INADDR_BROADCAST << (32 - mask.width());
_min._addr &= bits;
_max._addr |= ~bits;
}
}
return *this;
}
bool IP4Range::load(string_view text) {
static const string_view SEPARATORS("/-");
auto idx = text.find_first_of(SEPARATORS);
if (idx != text.npos) {
if (idx + 1 < text.size()) { // must have something past the separator or it's bogus.
if ('/' == text[idx]) {
metric_type 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;
}
}
} else if (_min.load(text.substr(0, idx)) && _max.load(text.substr(idx + 1))) {
return true;
}
}
} else if (_min.load(text)) {
_max = _min;
return true;
}
this->clear();
return false;
}
IPMask IP4Range::network_mask() const {
NetSource nets { *this };
if (! nets.empty() && (*nets).as_range() == *this) {
return nets->mask();
}
return {}; // default constructed (invalid) mask.
}
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 >>= 1;
_mask._addr |= 1U << (IP4Addr::WIDTH - 1); // put top bit back.
++_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();
if (cidr == 0) {
_min = metric_type::MIN;
_max = metric_type::MAX;
} else if (cidr < 64) { // only upper bytes affected, lower bytes are forced.
auto bits = FULL_MASK << (64 - cidr);
_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._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._store[1] &= bits;
_max._addr._store[1] |= ~bits;
}
}
return *this;
}
bool IP6Range::load(std::string_view text) {
static const string_view SEPARATORS("/-");
auto idx = text.find_first_of(SEPARATORS);
if (idx != text.npos) {
if (idx + 1 < text.size()) { // must have something past the separator or it's bogus.
if ('/' == text[idx]) {
metric_type 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;
}
}
} else if (_min.load(text.substr(0, idx)) && _max.load(text.substr(idx + 1))) {
return true;
}
}
} else if (_min.load(text)) {
_max = _min;
return true;
}
this->clear();
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);
if (idx != text.npos) {
if (text[idx] == '.') {
if (_range._ip4.load(text)) {
_family = AF_INET;
return true;
}
} else {
if (_range._ip6.load(text)) {
_family = AF_INET6;
return true;
}
}
}
return false;
}
IPAddr IPRange::min() const {
switch (_family) {
case AF_INET: return _range._ip4.min();
case AF_INET6: return _range._ip6.min();
default: break;
}
return {};
}
IPAddr IPRange::max() const {
switch (_family) {
case AF_INET: return _range._ip4.max();
case AF_INET6: return _range._ip6.max();
default: break;
}
return {};
}
bool IPRange::empty() const {
switch (_family) {
case AF_INET: return _range._ip4.empty();
case AF_INET6: return _range._ip6.empty();
default: break;
}
return true;
}
IPMask IPRange::network_mask() const {
switch (_family) {
case AF_INET: return _range._ip4.network_mask();
case AF_INET6: return _range._ip6.network_mask();
default: break;
}
return {};
}
IPMask IP6Range::network_mask() const {
NetSource nets { *this };
if (! nets.empty() && (*nets).as_range() == *this) {
return nets->mask();
}
return {}; // default constructed (invalid) mask.
}
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