ADDED:
* num-addrs subcommand, to get the number of hosts/addresses in a given
  prefix length
* get-net subcommand, to more easily get a single subnet from either the
  beginning or the end of a prefix. (MUCH FASTER than CIDR-splitting!)
This commit is contained in:
brent saner
2025-04-13 18:25:32 -04:00
parent c05f9c4d47
commit 860ad5842b
16 changed files with 334 additions and 78 deletions

View File

@@ -1,6 +1,7 @@
package netsplit
import (
`bytes`
"encoding/json"
"encoding/xml"
"fmt"
@@ -298,6 +299,113 @@ func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSe
return
}
/*
LastSubnetNet returns the last subnet of prefix length pfxLen from network pfx.
lastSubnet will be nil if pfx is nil or invalid.
*/
func LastSubnetNet(pfx *net.IPNet, pfxLen uint8) (lastSubnet *net.IPNet, err error) {
var nPfx netip.Prefix
var ok bool
if pfx == nil {
return
}
if nPfx, ok = netipx.FromStdIPNet(pfx); !ok {
return
}
if !nPfx.IsValid() {
return
}
if nPfx, err = LastSubnetPfx(nPfx, pfxLen); err != nil {
return
}
if !nPfx.IsValid() {
return
}
lastSubnet = netipx.PrefixIPNet(nPfx)
return
}
// LastSubnetPfx is exactly like LastSubnetNet but using netip.Prefix instead.
func LastSubnetPfx(pfx netip.Prefix, pfxLen uint8) (lastSubnet netip.Prefix, err error) {
var ok bool
var pfxBytes []byte
var bitOffset uint
var offset *big.Int
var ipInt *big.Int
var ipBytes []byte
var byteLen int
var lastNet *net.IPNet
var maxBitLen uint8 = maxBitsv4
if !pfx.IsValid() {
return
}
pfx = pfx.Masked()
if pfx.Addr().Is6() || pfxLen > maxBitsv4 {
maxBitLen = maxBitsv6
}
if pfxLen > maxBitLen {
err = ErrBadPrefixLen
return
}
if pfxLen < uint8(pfx.Bits()) {
err = ErrBigPrefix
return
}
if pfxBytes, err = pfx.Addr().MarshalBinary(); err != nil {
return
}
byteLen = pfx.Addr().BitLen() / 8
bitOffset = uint(pfxLen - uint8(pfx.Bits()))
offset = new(big.Int).Lsh(big.NewInt(1), bitOffset)
offset.Sub(offset, big.NewInt(1))
// Cast the prefix (as represented as bytes) into a *big.Int for some number magic.
ipInt = new(big.Int).SetBytes(pfxBytes)
// Shift to the first "address" in the prefix/mask it.
offset.Lsh(offset, uint(pfx.Addr().BitLen()-int(pfxLen)))
// Now add the offset to the base network.
ipInt.Add(ipInt, offset)
// If the base address starts at the "0 address" (e.g. 0.0.0.0 or :: etc....),
// this can cause some strange behavior when casting the *big.Int to bytes.
// So it gets left-null-padded to the appropriate length for the inet family.
ipBytes = ipInt.Bytes()
if len(ipBytes) < byteLen {
ipBytes = append(
bytes.Repeat([]byte{0x00}, byteLen-len(ipBytes)),
ipBytes...,
)
}
// Create an explicit net.IPNet...
lastNet = &net.IPNet{
IP: net.IP(ipBytes),
Mask: net.CIDRMask(int(pfxLen), pfx.Addr().BitLen()),
}
// And then make it a netip.Prefix.
if lastSubnet, ok = netipx.FromStdIPNet(lastNet); !ok {
return
}
if !lastSubnet.IsValid() {
return
}
return
}
/*
MaskExpand expands a net.IPMask's string format.
Like AddrExpand but for netmasks.
@@ -391,29 +499,29 @@ func MaskInvert(mask net.IPMask) (inverted net.IPMask) {
Note that for the single-host prefix (/32 for IPv4, /128 for IPv6), numAddrs will *always* be 1.
For point-to-point prefix (IPv4 /31, IPv6 /127), numAddrs will *ALWAYS* be 2.
*/
func NumAddrsIn(prefixLen uint8, isIpv6, inclNet, inclBcast bool) (numAddrs *big.Int, err error) {
func NumAddrsIn(pfxLen uint8, isIpv6, inclNet, inclBcast bool) (numAddrs *big.Int, err error) {
var numBits uint
var numRemoved int64
var maxBitLen uint8 = maxBitsv4
if isIpv6 {
if isIpv6 || pfxLen > maxBitsv4 {
maxBitLen = maxBitsv6
}
if prefixLen > maxBitLen {
if pfxLen > maxBitLen {
err = ErrBadPrefixLen
return
}
if prefixLen == maxBitLen {
if pfxLen == maxBitLen {
numAddrs = big.NewInt(1)
return
}
if (prefixLen + 1) == maxBitLen {
if (pfxLen + 1) == maxBitLen {
numAddrs = big.NewInt(2)
return
}
numBits = uint(maxBitLen - prefixLen)
numBits = uint(maxBitLen - pfxLen)
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
@@ -451,6 +559,9 @@ func NumAddrsNet(pfx *net.IPNet, inclNet, inclBcast bool) (numAddrs *big.Int) {
if nPfx, ok = netipx.FromStdIPNet(pfx); !ok {
return
}
if !nPfx.IsValid() {
return
}
numAddrs = NumAddrsPfx(nPfx, inclNet, inclBcast)
@@ -460,21 +571,16 @@ func NumAddrsNet(pfx *net.IPNet, inclNet, inclBcast bool) (numAddrs *big.Int) {
// NumAddrsPfx is the exact same as NumAddrsNet but for a net/netip.Prefix instead.
func NumAddrsPfx(pfx netip.Prefix, inclNet, inclBcast bool) (numAddrs *big.Int) {
var numBits uint
var numRemoved int64
var err error
numBits = uint(pfx.Addr().BitLen() - pfx.Bits())
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
if !inclNet {
numRemoved++
if !pfx.IsValid() {
return
}
if !inclBcast {
numRemoved++
}
if numRemoved > 0 {
_ = numAddrs.Sub(numAddrs, big.NewInt(numRemoved))
// Since we're dealing with existing prefixes/networks, we should never get an error.
if numAddrs, err = NumAddrsIn(uint8(pfx.Bits()), pfx.Addr().Is6(), inclNet, inclBcast); err != nil {
// But *somehow* in case we do...
panic(err)
}
return