net/addr/subnet: Add class for representing a network subnet
This makes it possible to represent and IPv4/IPv6 CIDR network segment
via a class. Provides useful functions to compute base address and
determine if addresses are inside the subnet.
Change-Id: Ib9d01e28b6c8a28ccb622fef87b217fc96daf905
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 1402818..d7cf82b 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -11,6 +11,7 @@
'stdplus/hash/tuple.hpp',
'stdplus/net/addr/ether.hpp',
'stdplus/net/addr/ip.hpp',
+ 'stdplus/net/addr/subnet.hpp',
'stdplus/numeric/endian.hpp',
'stdplus/pinned.hpp',
'stdplus/raw.hpp',
diff --git a/include/stdplus/net/addr/subnet.hpp b/include/stdplus/net/addr/subnet.hpp
new file mode 100644
index 0000000..862237b
--- /dev/null
+++ b/include/stdplus/net/addr/subnet.hpp
@@ -0,0 +1,194 @@
+#include <stdplus/net/addr/ip.hpp>
+#include <stdplus/numeric/endian.hpp>
+
+#include <limits>
+#include <type_traits>
+
+namespace stdplus
+{
+namespace detail
+{
+
+// AddressSan doesn't understand our masking of shift UB
+__attribute__((no_sanitize("undefined"))) constexpr uint32_t
+ addr32Mask(std::ptrdiff_t pfx) noexcept
+{
+ // Positive prefix check + mask to handle UB when the left shift becomes
+ // more than 31 bits
+ return hton(static_cast<uint32_t>(-int32_t{pfx > 0}) & ~uint32_t{0}
+ << (32 - pfx));
+}
+
+constexpr In4Addr addrToSubnet(In4Addr a, std::size_t pfx) noexcept
+{
+ return In4Addr{in_addr{a.s4_addr32 & addr32Mask(pfx)}};
+}
+
+constexpr In6Addr addrToSubnet(In6Addr a, std::size_t pfx, std::size_t i = 0,
+ std::size_t s = 0, In6Addr ret = {}) noexcept
+{
+ if (s + 32 < pfx)
+ {
+ ret.s6_addr32[i] = a.s6_addr32[i];
+ return addrToSubnet(a, pfx, i + 1, s + 32, ret);
+ }
+ ret.s6_addr32[i] = a.s6_addr32[i] & addr32Mask(pfx - s);
+ return ret;
+}
+
+constexpr InAnyAddr addrToSubnet(InAnyAddr a, std::size_t pfx) noexcept
+{
+ return std::visit([&](auto av) { return InAnyAddr{addrToSubnet(av, pfx)}; },
+ a);
+}
+
+constexpr bool subnetContains(auto, auto, std::size_t) noexcept
+{
+ return false;
+}
+
+template <typename T>
+constexpr bool subnetContains(T l, T r, std::size_t pfx) noexcept
+{
+ return addrToSubnet(l, pfx) == addrToSubnet(r, pfx);
+}
+
+constexpr bool subnetContains(InAnyAddr l, auto r, std::size_t pfx) noexcept
+{
+ return std::visit([&](auto v) { return detail::subnetContains(v, r, pfx); },
+ l);
+}
+
+constexpr std::size_t addrBits(auto a) noexcept
+{
+ return sizeof(a) << 3;
+}
+
+void invalidSubnetPfx(std::size_t pfx);
+
+template <typename Addr, typename Pfx>
+class Subnet46
+{
+ private:
+ static constexpr inline std::size_t maxPfx = sizeof(Addr) * 8;
+ static_assert(std::is_unsigned_v<Pfx> && std::is_integral_v<Pfx>);
+ static_assert(std::numeric_limits<Pfx>::max() >= maxPfx);
+
+ Addr addr;
+ Pfx pfx;
+
+ public:
+ constexpr Subnet46(Addr addr, Pfx pfx) : addr(addr), pfx(pfx)
+ {
+ if (addrBits(addr) < pfx)
+ {
+ invalidSubnetPfx(pfx);
+ }
+ }
+
+ constexpr auto getAddr() const noexcept
+ {
+ return addr;
+ }
+
+ constexpr auto getPfx() const noexcept
+ {
+ return pfx;
+ }
+
+ constexpr bool operator==(Subnet46 rhs) const noexcept
+ {
+ return addr == rhs.addr && pfx == rhs.pfx;
+ }
+
+ constexpr Addr network() const noexcept
+ {
+ return addrToSubnet(addr, pfx);
+ }
+
+ constexpr bool contains(Addr addr) const noexcept
+ {
+ return addrToSubnet(this->addr, pfx) == addrToSubnet(addr, pfx);
+ }
+};
+
+} // namespace detail
+
+using Subnet4 = detail::Subnet46<In4Addr, uint8_t>;
+using Subnet6 = detail::Subnet46<In6Addr, uint8_t>;
+
+class SubnetAny
+{
+ private:
+ InAnyAddr addr;
+ uint8_t pfx;
+
+ public:
+ constexpr SubnetAny(auto addr, uint8_t pfx) : addr(addr), pfx(pfx)
+ {
+ if (detail::addrBits(addr) < pfx)
+ {
+ detail::invalidSubnetPfx(pfx);
+ }
+ }
+ constexpr SubnetAny(InAnyAddr addr, uint8_t pfx) : addr(addr), pfx(pfx)
+ {
+ if (std::visit([](auto v) { return detail::addrBits(v); }, addr) < pfx)
+ {
+ detail::invalidSubnetPfx(pfx);
+ }
+ }
+
+ template <typename T, typename S>
+ constexpr SubnetAny(detail::Subnet46<T, S> o) noexcept :
+ addr(o.getAddr()), pfx(o.getPfx())
+ {}
+
+ constexpr auto getAddr() const noexcept
+ {
+ return addr;
+ }
+
+ constexpr auto getPfx() const noexcept
+ {
+ return pfx;
+ }
+
+ template <typename T, typename S>
+ constexpr bool operator==(detail::Subnet46<T, S> rhs) const noexcept
+ {
+ return addr == rhs.getAddr() && pfx == rhs.getPfx();
+ }
+ constexpr bool operator==(SubnetAny rhs) const noexcept
+ {
+ return addr == rhs.addr && pfx == rhs.pfx;
+ }
+
+ constexpr InAnyAddr network() const noexcept
+ {
+ return detail::addrToSubnet(addr, pfx);
+ }
+
+ constexpr bool contains(In4Addr addr) const noexcept
+ {
+ return detail::subnetContains(this->addr, addr, pfx);
+ }
+ constexpr bool contains(in_addr addr) const noexcept
+ {
+ return contains(In4Addr{addr});
+ }
+ constexpr bool contains(In6Addr addr) const noexcept
+ {
+ return detail::subnetContains(this->addr, addr, pfx);
+ }
+ constexpr bool contains(in6_addr addr) const noexcept
+ {
+ return contains(In6Addr{addr});
+ }
+ constexpr bool contains(InAnyAddr addr) const noexcept
+ {
+ return std::visit([&](auto v) { return contains(v); }, addr);
+ }
+};
+
+} // namespace stdplus