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 }