From 3c239a4d093a698756c805da7dea609e94e420bb Mon Sep 17 00:00:00 2001 From: brent saner Date: Sun, 6 Apr 2025 01:31:51 -0400 Subject: [PATCH] v0.2.0 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. --- cmd/subnetter/args.go | 24 +++++++++++++++++++----- cmd/subnetter/funcs.go | 4 +++- cmd/subnetter/main.go | 32 ++++++++++++++++++++++++++++++-- netsplit/conts.go | 6 ++++++ netsplit/funcs.go | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 8 deletions(-) diff --git a/cmd/subnetter/args.go b/cmd/subnetter/args.go index 3876d1c..7573efd 100644 --- a/cmd/subnetter/args.go +++ b/cmd/subnetter/args.go @@ -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=-"` } diff --git a/cmd/subnetter/funcs.go b/cmd/subnetter/funcs.go index 96d7809..843f45b 100644 --- a/cmd/subnetter/funcs.go +++ b/cmd/subnetter/funcs.go @@ -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] diff --git a/cmd/subnetter/main.go b/cmd/subnetter/main.go index 82f83fe..0ea9390 100644 --- a/cmd/subnetter/main.go +++ b/cmd/subnetter/main.go @@ -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) } diff --git a/netsplit/conts.go b/netsplit/conts.go index f8c8f53..067c086 100644 --- a/netsplit/conts.go +++ b/netsplit/conts.go @@ -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 diff --git a/netsplit/funcs.go b/netsplit/funcs.go index e1b9707..0615233 100644 --- a/netsplit/funcs.go +++ b/netsplit/funcs.go @@ -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) {