99 lines
2.8 KiB
Go
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
|
|
}
|