ADDED:
* num-nets subcommand' this is MUCH much faster than actually splitting
  if you're trying to figure out how many times a given subnet fits into
  a network.
This commit is contained in:
brent saner 2025-04-06 01:31:51 -04:00
parent 701b598b1c
commit 3c239a4d09
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 91 additions and 8 deletions

View File

@ -5,8 +5,9 @@ type Args struct {
SplitCIDR SplitCIDRArgs `command:"split-cidr" alias:"se" description:"Split a network into as many equal subnets of prefix size N as possible." validate:"omitempty"`
SplitHost SplitHostArgs `command:"split-hosts" alias:"sh" description:"Split a network into N total number of hosts *per subnet* as cleanly/evenly as possible. (VERY easy to run out of memory for IPv6 prefixes; be sure to specify very small network!)" validate:"omitempty"`
SplitSubnets SplitSubnetArgs `command:"split-nets" alias:"sn" description:"Split a network into N number of subnets as cleanly as possible." validate:"omitempty"`
VLSM VLSMArgs `command:"vlsm" alias:"sv" description:"Use VLSM (Variable-Length Subnet Masks) to split a network into differently sized subnets." validate:"omitempty"`
ExplicitNetwork XNetArgs `command:"net" alias:"xn" description:"Print information about an explicit network address." validate:"omitempty"`
VLSM VLSMArgs `command:"split-vlsm" alias:"sv" alias:"vlsm" description:"Use VLSM (Variable-Length Subnet Masks) to split a network into differently sized subnets." validate:"omitempty"`
ExplicitNetwork XNetArgs `command:"net" alias:"xn" alias:"net" description:"Print information about an explicit network address." validate:"omitempty"`
NumNets NNetArgs `command:"num-nets" alias:"nn" alias:"nets" description:"Return the number of subnets of a given size that can fit into a given network size. This is MUCH, MUCH FASTER than splitting (if you do not need addressing)." validate:"omitempty"`
Parse ParseArgs `command:"parse" alias:"p" alias:"read" alias:"convert" description:"Parse/convert output from a previous subnetter run." validate:"omitempty"`
Table TableArgs `command:"table" alias:"t" alias:"tab" alias:"tbl" description:"Show prefix summaries (by default both IPv4 and IPv6)." validate:"omitempty"`
Check CheckArgs `command:"reserved" alias:"r" description:"Check if a subnet is reserved per IANA/RFC." validate:"omitempty"`
@ -16,7 +17,7 @@ type verArgs struct {
DetailVersion bool `short:"V" long:"detail" description:"Print detailed version info and exit."`
}

type common struct {
type commonBase struct {
cacheArgs
SuppressRemaining bool `short:"r" long:"no-remaining" description:"Don't show leftover/unallocated/remaining space."`
Plain bool `short:"p" long:"plain" description:"Show plain output instead of unicode (only used if -f/--format=pretty)."`
@ -26,7 +27,11 @@ type common struct {
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
reservedArgs
AllowHostNet bool `short:"H" long:"allow-host" description:"If specified, do not warn about host bits. Host bits are always removed for subnetting (as otherwise there would be errors); this is only used only for output."`
Network Net `positional-args:"yes" required:"true" description:"The network/parent prefix to operate on." validate:"required"`
}

type common struct {
commonBase
Network Net `positional-args:"yes" required:"true" description:"The network/parent prefix to operate on." validate:"required"`
}

type reservedArgs struct {
@ -44,8 +49,17 @@ type cacheArgs struct {
DoResCache bool `short:"c" long:"cache-reservations" env:"SBNTR_RSVCACHE" description:"Enable caching/cache lookup for reservation data."`
}

type NNetArgs struct {
Verbose bool `short:"v" long:"verbose" description:"Be verbose (more ideal for logging)."`
NoV6Check bool `short:"6" long:"no-v6" description:"If specified, do not indicate if the subnetting is IPv6 only (true) or not (false; dual-stack/IPv4 supported)."`
Sizes struct {
SubnetSize uint8 `required:"1" validate:"required,le=128,gtefield=NetworkSize"`
NetworkSize uint8 `required:"1" validate:"required,le=128,ltefield=SubnetSize"`
} `positional-args:"yes" required:"2" validate:"required"`
}

type ParseArgs struct {
splitArgs
commonBase
InFile string `short:"i" long:"input" default:"-" description:"Input file to parse. Default is '-' (for STDIN)." required:"true" validate:"required,filepath|eq=-"`
}


View File

@ -236,7 +236,9 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem

if args == nil {
args = &common{
Separator: "\n",
commonBase: commonBase{
Separator: "\n",
},
}
}
fmts = sectFmts[args.Plain]

View File

@ -33,6 +33,8 @@ func main() {
var remaining *netipx.IPSet
var buf *bytes.Buffer
var res *netsplit.StructuredResults
var numNets uint
var v6Only bool
var noStrict bool
var strictErr error
var reservations map[netip.Prefix]*netsplit.IANAAddrNetResRecord
@ -79,6 +81,27 @@ func main() {
log.Panicln(err)
}
return
case "num-nets":
if numNets, v6Only, err = netsplit.NumNets(
args.NumNets.Sizes.SubnetSize,
args.NumNets.Sizes.NetworkSize,
); err != nil {
log.Panicln(err)
}
if !args.NumNets.Verbose {
fmt.Printf("%d\n", numNets)
if !args.NumNets.NoV6Check {
fmt.Println(v6Only)
}
} else {
fmt.Printf("Network Size:\t\t\t%d\n", args.NumNets.Sizes.NetworkSize)
fmt.Printf("Subnet Size:\t\t\t%d\n", args.NumNets.Sizes.SubnetSize)
fmt.Printf("Number of Subnets:\t\t%d\n", numNets)
if !args.NumNets.NoV6Check {
fmt.Printf("Subnetting is IPv6-Only:\t%v\n", v6Only)
}
}
return
case "reserved":
if origPfx, err = netip.ParsePrefix(args.Check.Network.Network); err != nil {
log.Panicln(err)
@ -136,7 +159,12 @@ func main() {
origPfx = *resPfx
}
pfx = netipx.PrefixIPNet(origPfx.Masked())
cmnArgs = args.Parse.common
cmnArgs = common{
commonBase: args.Parse.commonBase,
Network: Net{
Network: res.Original.String(),
},
}
if err = printNets(&origPfx, pfx, nets, remaining, &cmnArgs, res.GetSplitter()); err != nil {
log.Panicln(err)
}
@ -185,7 +213,7 @@ func main() {
PrefixLength: args.SplitCIDR.Prefix,
BaseSplitter: new(netsplit.BaseSplitter),
}
case "vlsm":
case "split-vlsm":
if err = validate.Struct(args.VLSM); err != nil {
log.Panicln(err)
}

View File

@ -8,6 +8,12 @@ import (
`github.com/go-resty/resty/v2`
)

const (
maxBitsv4 uint8 = 32
maxBitsv6 uint8 = 128
maxBits uint8 = maxBitsv6
)

const (
cachedirEnvName string = "SBNTR_RSVCACHE_DIR"
// https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"encoding/xml"
"fmt"
`math`
"net"
"net/netip"
"strings"
@ -375,6 +376,38 @@ func MaskInvert(mask net.IPMask) (inverted net.IPMask) {
return
}

/*
NumNets returns the number of times prefix size subnet fits into prefix size network.

It will error if network is larger than 128 or if subnet is smaller than network.

This is MUCH more performant than splitting out an actual network into explicit subnets,
and does not require an actual network.
*/
func NumNets(subnet, network uint8) (numNets uint, ipv6Only bool, err error) {

var x float64

// network cannot be higher than 128, as that's the maximum for IPv6.
if network > maxBits {
err = ErrBadPrefixLen
return
}
if subnet < network {
err = ErrBigPrefix
return
}
if network > maxBitsv4 {
ipv6Only = true
}

x = float64(subnet - network)

numNets = uint(math.Pow(2, x))

return
}

// Parse parses b for JSON/XML/YAML and tries to return a StructuredResults from it.
func Parse(b []byte) (s *StructuredResults, err error) {