go_subnetter/netsplit/funcs_hostsplitter.go
brent saner 3c1bc832c0
v0.2.1
FIXED:
* host splitter wasn't working quite correctly; this has been fixed.
2025-04-06 18:26:18 -04:00

99 lines
2.8 KiB
Go

package netsplit
import (
"math/big"
"net/netip"
"go4.org/netipx"
)
/*
Split splits the network defined in a HostSplitter alongside its configuration and performs the subnetting.
This strategy attempts to split the network into subnets of equal number of hosts.
remaining may or may not be nil depending on if the number of hosts can fit cleanly within equal network sizes on boundaries.
An ErrBadNumHosts will be returned if the number of hosts does not match the *exact* number of addresses per spec in a prefix.
*/
func (h *HostSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
var pfx netip.Prefix
var tgt *big.Int
var splitCidr int
var hosts *big.Int
var found bool
var cs *CIDRSplitter
if h == nil || h.NumberHosts == 0 || h.BaseSplitter == nil || h.network == nil {
return
}
pfx, _ = netipx.FromStdIPNet(h.network)
tgt = new(big.Int)
tgt.SetUint64(uint64(h.NumberHosts))
if NumAddrsPfx(pfx, h.InclNetAddr, h.InclBcastAddr).Cmp(tgt) < 0 {
// The number of hosts per-subnet exceeds the number of addresses in the specified network.
err = ErrNoNetSpace
return
}
/*
Iterate up through prefix lengths for the inet family's maximum length, getting larger and larger,
until we reach the first prefix that can contain tgt.
If we reach h.network.Bits(), we are forced to use that.
(Any case otherwise should be handled by the above checks.)
*/
for splitCidr = pfx.Addr().BitLen(); splitCidr >= pfx.Bits(); splitCidr-- {
if hosts, err = NumAddrsIn(uint8(splitCidr), pfx.Addr().Is6(), h.InclNetAddr, h.InclBcastAddr); err != nil {
return
}
if hosts.Cmp(tgt) >= 0 {
found = true
break
}
}
if !found {
// Pragmatically, we should never be able to get to this code.
err = ErrNoNetSpace
return
}
// Now that we have an appropriate prefix length for splitting, we can offload a huge portion of that to a CIDRSplitter.
cs = &CIDRSplitter{
PrefixLength: uint8(splitCidr),
BaseSplitter: h.BaseSplitter,
}
if nets, remaining, err = cs.Split(); err != nil {
return
}
// If strict mode is enabled, we then need to match the number of hosts exactly in the subnet.
if !h.Strict {
return
}
// First off, if remaining is not nil/empty, that immediately fails strict.
if remaining != nil && remaining.Prefixes() != nil && len(remaining.Prefixes()) != 0 {
err = &SplitErr{
Wrapped: ErrBadNumHosts,
Nets: nets,
Remaining: remaining,
LastSubnet: nil,
RequestedPrefixLen: uint8(splitCidr),
}
return
}
// Then we check the cidr we split on, and check its number of hosts.
if hosts.Cmp(tgt) != 0 {
err = &SplitErr{
Wrapped: ErrBadNumHosts,
Nets: nets,
Remaining: remaining,
LastSubnet: nil,
RequestedPrefixLen: uint8(splitCidr),
}
return
}
return
}