FIXED:
* host splitter wasn't working quite correctly; this has been fixed.
This commit is contained in:
brent saner
2025-04-06 18:26:18 -04:00
parent fd344f3b8e
commit 3c1bc832c0
7 changed files with 221 additions and 90 deletions

View File

@@ -1,12 +1,9 @@
package netsplit
import (
"fmt"
"math/big"
"net"
"net/netip"
"github.com/projectdiscovery/mapcidr"
"go4.org/netipx"
)
@@ -16,54 +13,84 @@ This strategy attempts to split the network into subnets of equal number of host
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 *addressable* range in a prefix.
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 sub netip.Prefix
var subPtr *netip.Prefix
var split []*net.IPNet
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
var found bool
var cs *CIDRSplitter
if h == nil || h.NumberHosts == 0 || h.BaseSplitter == nil || h.network == nil {
return
}
if split, err = mapcidr.SplitIPNetByNumber(h.network, int(h.NumberHosts)); err != nil {
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
}
fmt.Println(split)
tgt = big.NewInt(0)
tgt.SetUint64(uint64(h.NumberHosts))
nets = make([]*netip.Prefix, len(split))
for idx, n := range split {
sub, _ = netipx.FromStdIPNet(n)
hosts = mapcidr.CountIPsInCIDR(false, false, n)
if hosts == nil || tgt.Cmp(hosts) != 0 {
err = &SplitErr{
Wrapped: ErrBadNumHosts,
Nets: nets,
Remaining: remaining,
LastSubnet: &sub,
RequestedPrefixLen: uint8(sub.Bits()),
}
ipsb.AddPrefix(sub)
} else {
subPtr = new(netip.Prefix)
*subPtr = sub
nets = append(nets, subPtr)
// 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),
}
nets[idx] = new(netip.Prefix)
*nets[idx] = sub
return
}
if remaining, err = ipsb.IPSet(); err != nil {
// 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
}