checking in- needs some refinement then done

This commit is contained in:
2025-02-09 23:07:25 -05:00
parent 64b669edc3
commit d8469533a7
22 changed files with 1110 additions and 115 deletions

View File

@@ -0,0 +1,2 @@
{{- /*gotype: subnetter/cmd/subnetter.ReservedResults*/ -}}
{{- $opts := . -}}

View File

@@ -1,4 +1,4 @@
{{- /*gotype: subnetter/cmd/subnetter.tableOpts*/ -}}
{{- /*gotype: subnetter/cmd/subnetter.TableArgs*/ -}}
{{- $opts := . -}}
{{- $numRows := 0 -}}
{{- if not $opts.NoIpv4 }}

View File

@@ -1,72 +1,96 @@
package main
type Args struct {
Version bool `short:"v" long:"version" description:"Print the version and exit."`
DetailVersion bool `short:"V" long:"detail" description:"Print detailed version info and exit."`
Version verArgs `command:"version" alias:"v" description:"Show version information." validate:"omitempty"`
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:"v" description:"Use VLSM (Variable-Length Subnet Masks) to split a network into differently sized subnets." validate:"omitempty"`
ExplicitNetwork XNetArgs `command:"net" alias:"n" description:"Print information about an explicit network address." 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"`
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"`
}
type outputOpts struct {
SuppressRemaining bool `short:"r" long:"no-remaining" description:"Don't show leftover/unallocated/remaining space.'"`
Verbose []bool `short:"v" long:"verbose" description:"Show verbose information if -f/--format=pretty. May be specified multiple times to increase verbosity (up to 3 levels)."`
Plain bool `short:"p" long:"plain" description:"Show plain output instead of unicode (only used if -f/--format=pretty)."`
Seperator string `short:"S" long:"seperator" default:"\n" description:"Separator between addresses; only used for -f/--format=pretty with no verbosity."`
Fmt string `short:"f" long:"format" choice:"json" choice:"pretty" choice:"yml" choice:"xml" default:"pretty" description:"Output format. 'pretty' is not intended to be parseable, either by subnetter or by external tooling."`
type verArgs struct {
DetailVersion bool `short:"V" long:"detail" description:"Print detailed version info and exit."`
}
type common struct {
outputOpts
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
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 to be split/subnetted." validate:"required"`
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)."`
Separator string `short:"S" long:"separator" default:"\n" description:"Separator between addresses; only used for -f/--format=pretty with no verbosity."`
Fmt string `short:"f" long:"format" choice:"json" choice:"pretty" choice:"yml" choice:"xml" default:"pretty" description:"Output format. 'pretty' is not intended to be parseable, either by subnetter or by external tooling."`
Verbose []bool `short:"v" long:"verbose" description:"Show verbose information if -f/--format=pretty. May be specified multiple times to increase verbosity (up to 3 levels)."`
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 reservedArgs struct {
NoRecursive bool `short:"u" long:"no-recursive" description:"Don't show reservations that are children subnets of the subnet(s). Only if -f/--format=pretty, always false for other formats."`
NoRevRecursive bool `short:"U" long:"no-rev-recursive" description:"Don't show reservations that are parents of the subnet(s). Only if -f/--format=pretty, always false for other formats."`
NoPrivate bool `short:"e" long:"no-private" description:"Consider private subnets of the subnet(s) to be reserved. If you are subnetting private address space, you probably want to leave this disabled. Only if -f/--format=pretty, always true otherwise."`
}
type splitArgs struct {
common
}
type cacheArgs struct {
CacheDir string `short:"C" long:"cache-dir" env:"SBNTR_RSVCACHE_DIR" description:"Cached reservation data directory. The default is platform/OS-specific."`
DoResCache bool `short:"c" long:"cache-reservations" env:"SBNTR_RSVCACHE" description:"Enable caching/cache lookup for reservation data."`
}
type ParseArgs struct {
outputOpts
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
AllowHostNet bool `short:"H" long:"allow-host" description:"If specified, do not warn about host bits."`
InFile string `short:"i" long:"input" default:"-" description:"Input file to parse. Default is '-' (for STDIN)." required:"true" validate:"required,filepath|eq=-"`
splitArgs
InFile string `short:"i" long:"input" default:"-" description:"Input file to parse. Default is '-' (for STDIN)." required:"true" validate:"required,filepath|eq=-"`
}
type SplitCIDRArgs struct {
Prefix uint8 `short:"s" long:"size" required:"true" description:"Prefix length/network size in bits (as CIDR number)." validate:"required"`
common
splitArgs
}
type SplitHostArgs struct {
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of hosts/assignable addresses in a subnet is not exactly -n/--num-hosts."`
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
common
splitArgs
}
type SplitSubnetArgs struct {
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of possible equally-sized subnets is not exactly -n/--num-nets."`
NumNets uint `short:"n" long:"num-nets" required:"true" description:"Number of networks." validate:"required"`
common
splitArgs
}
type TableArgs struct {
tableOpts
Notes bool `short:"n" long:"notes" description:"Include notes about prefixes (as a separate table)."`
Legacy bool `short:"l" long:"legacy" description:"Include legacy/obsolete/deprecated information (as separate table(s))."`
Plain bool `short:"p" long:"plain" description:"Show plain table output instead of unicode."`
NoV4Mask bool `short:"M" long:"no-mask" description:"Do not include netmasks for IPv4."`
NoIpv6 bool `short:"4" long:"ipv4" description:"Only show IPv4 table(s)."`
NoIpv4 bool `short:"6" long:"ipv6" description:"Only show IPv6 table(s)."`
}
type CheckArgs struct {
cacheArgs
reservedArgs
Plain bool `short:"p" long:"plain" description:"Show plain output instead of unicode (only used if -f/--format=pretty)."`
Fmt string `short:"f" long:"format" choice:"json" choice:"pretty" choice:"yml" choice:"xml" default:"pretty" description:"Output format. 'pretty' is not intended to be parseable, either by subnetter or by external tooling."`
Network Net `positional-args:"yes" required:"true" description:"The network/parent prefix to operate on." validate:"required"`
}
type XNetArgs struct {
Plain bool `short:"p" long:"plain" description:"Show plain output instead of unicode (only used if -f/--format=pretty)."`
Seperator string `short:"S" long:"seperator" default:"\n" description:"Separator between addresses; only used for -f/--format=pretty with no verbosity."`
Fmt string `short:"f" long:"format" choice:"json" choice:"pretty" choice:"yml" choice:"xml" default:"pretty" description:"Output format. 'pretty' is not intended to be parseable, either by subnetter or by external tooling."`
Verbose []bool `short:"v" long:"verbose" description:"Show verbose information (if -n/--network is specified). May be specified multiple times to increase verbosity (up to 2 levels)."`
Network Net `positional-args:"yes" required:"true" description:"The network address to print information about." validate:"required"`
common
}
type VLSMArgs struct {
Asc bool `short:"a" long:"asc" description:"If specified, place smaller networks (larger prefixes) at the beginning. You almost assuredly do not want to do this."`
Asc bool `short:"A" long:"ascending" description:"If specified, place smaller networks (larger prefixes) at the beginning. You almost assuredly do not want to do this."`
Sizes []uint8 `short:"s" long:"size" required:"true" description:"Prefix lengths. May be specified multiple times." validate:"required"`
common
splitArgs
}
type Net struct {

View File

@@ -10,6 +10,7 @@ import (
"net"
"net/netip"
"os"
`sort`
"strings"
"time"
@@ -217,6 +218,11 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
var masked netip.Prefix
var remPfxs []*netip.Prefix
var invertedMask net.IPMask
var resIdx int
var resPfx netip.Prefix
var resRec *netsplit.IANAAddrNetResRecord
var reservedList []*netip.Prefix
var reserved map[netip.Prefix]*netsplit.IANAAddrNetResRecord
var res *netsplit.StructuredResults
var verb = -1
var fmts []string
@@ -230,9 +236,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
if args == nil {
args = &common{
outputOpts: outputOpts{
Seperator: "\n",
},
Separator: "\n",
}
}
fmts = sectFmts[args.Plain]
@@ -240,9 +244,9 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
sectSep2 = fmts[1]
// sectSep3 = fmts[2]
if args.outputOpts.Verbose != nil {
if args.Verbose != nil {
verb = 0
for _, i := range args.outputOpts.Verbose {
for _, i := range args.Verbose {
if i {
verb++
}
@@ -269,7 +273,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
verb = -1
}
if args.outputOpts.Fmt == "pretty" {
if args.Fmt == "pretty" {
// "Human"-formatted
// Header
@@ -307,6 +311,73 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
fmt.Println(sectSep1)
}
// Reservations
if !args.AllowReserved && verb >= 1 {
fmt.Println()
fmt.Println(sectSep1)
fmt.Println("Reserved Subnets in Selection:")
if reserved, err = netsplit.CheckReserved(nets, !args.NoRevRecursive, !args.NoRecursive, !args.NoPrivate); err != nil {
return
}
if reserved == nil || len(reserved) == 0 {
fmt.Println("(No reserved subnets found; good job!)")
} else {
reservedList = make([]*netip.Prefix, len(reserved))
for resPfx, _ = range reserved {
reservedList[resIdx] = &resPfx
resIdx++
}
sort.SliceStable(
reservedList,
func(i, j int) (isLess bool) {
isLess = reservedList[i].String() < reservedList[j].String()
return
},
)
fmt.Println("The following reserved subnets were found to be conflicting with your allocation(s).")
for _, n := range reservedList {
resRec = reserved[*n]
fmt.Printf("\tNetwork: %s\n", n.String())
fmt.Printf("\t\tReservation: %s\n", resRec.Name)
fmt.Println("\t\tAllocations:")
for _, i := range resRec.Networks.Prefixes {
fmt.Printf("\t\t\t%s\n", i.String())
}
if verb >= 2 {
fmt.Println("\t\tReserved By:")
for _, i := range resRec.Spec.References {
fmt.Printf("\t\t\t%s %s\n", i.Type, i.Reference)
}
}
if verb >= 3 {
if resRec.Source != nil && resRec.Source.Evaluated != nil && resRec.Source.Applicable != nil && *resRec.Source.Applicable {
fmt.Printf("\t\tIs Source:\t%v\n", *resRec.Source.Evaluated)
}
if resRec.Dest != nil && resRec.Dest.Evaluated != nil && resRec.Dest.Applicable != nil && *resRec.Dest.Applicable {
fmt.Printf("\t\tIs Destination:\t%v\n", *resRec.Dest.Evaluated)
}
if resRec.Forwardable != nil && resRec.Forwardable.Evaluated != nil && resRec.Forwardable.Applicable != nil && *resRec.Forwardable.Applicable {
fmt.Printf("\t\tIs Forwardable:\t%v\n", *resRec.Forwardable.Evaluated)
}
if resRec.GlobalReach != nil && resRec.GlobalReach.Evaluated != nil && resRec.GlobalReach.Applicable != nil && *resRec.GlobalReach.Applicable {
fmt.Printf("\t\tIs Globally Reachable:\t%v\n", *resRec.GlobalReach.Evaluated)
}
if resRec.ProtoReserved != nil && resRec.ProtoReserved.Evaluated != nil && resRec.ProtoReserved.Applicable != nil && *resRec.ProtoReserved.Applicable {
fmt.Printf("\t\tIs Reserved by Protocol:\t%v\n", *resRec.ProtoReserved.Evaluated)
}
fmt.Printf("\t\tAllocated: %s\n", time.Time(resRec.Allocation).String())
if resRec.Updated != nil {
fmt.Printf("\t\tUpdated: %s\n", time.Time(*resRec.Updated).String())
}
if resRec.Termination != nil {
fmt.Printf("\t\tUpdated: %s\n", time.Time(*resRec.Termination).String())
}
}
}
}
fmt.Println(sectSep1)
}
// Allocations
if verb >= 1 {
fmt.Println()
@@ -319,7 +390,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
}
} else {
for _, n := range nets {
fmt.Print(resFromPfx(n).pretty(verb, 1, args.outputOpts.Seperator, "\t", false, args.Plain))
fmt.Print(resFromPfx(n).pretty(verb, 1, args.Separator, "\t", false, args.Plain))
}
}
if verb >= 1 {
@@ -327,7 +398,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
}
// Remaining
if !args.outputOpts.SuppressRemaining {
if !args.SuppressRemaining {
if verb >= 1 {
fmt.Println()
fmt.Println(sectSep1)
@@ -343,7 +414,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
return
}
for _, n := range remaining.Prefixes() {
fmt.Print(resFromPfx(&n).pretty(verb, 1, args.outputOpts.Seperator, "\t", true, args.Plain))
fmt.Print(resFromPfx(&n).pretty(verb, 1, args.Separator, "\t", true, args.Plain))
}
}
if verb >= 1 {
@@ -355,7 +426,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
if res, err = netsplit.Contain(orig, nets, remaining, splitter); err != nil {
return
}
switch strings.ToLower(args.outputOpts.Fmt) {
switch strings.ToLower(args.Fmt) {
case "json":
if b, err = json.MarshalIndent(res, "", " "); err != nil {
return
@@ -400,6 +471,11 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
return
}
func printReserved(nets []*netip.Prefix, remaining *netipx.IPSet, args *common) (err error) {
return
}
func printSplitErr(e *netsplit.SplitErr) {
if e == nil {

View File

@@ -34,8 +34,8 @@ func main() {
var res *netsplit.StructuredResults
var noStrict bool
var strictErr error
var splitErr = new(netsplit.SplitErr)
var parser = flags.NewParser(args, flags.Default)
var splitErr *netsplit.SplitErr = new(netsplit.SplitErr)
var parser *flags.Parser = flags.NewParser(args, flags.Default)
if _, err = parser.Parse(); err != nil {
switch flagsErr := err.(type) {
@@ -55,35 +55,21 @@ func main() {
log.Panicln(err)
}
// If args.Version or args.DetailVersion are true, just print them and exit.
if args.DetailVersion || args.Version {
if args.Version {
fmt.Println(version.Ver.Short())
return
} else if args.DetailVersion {
switch parser.Active.Name {
case "version":
if args.Version.DetailVersion {
fmt.Println(version.Ver.Detail())
return
} else {
fmt.Println(version.Ver.Short())
return
}
}
switch parser.Active.Name {
case "net":
if origPfx, err = netip.ParsePrefix(args.ExplicitNetwork.Network.Network); err != nil {
log.Panicln(err)
}
pfx = netipx.PrefixIPNet(origPfx.Masked())
cmnArgs = common{
outputOpts: outputOpts{
SuppressRemaining: true,
Plain: args.ExplicitNetwork.Plain,
Verbose: args.ExplicitNetwork.Verbose,
Seperator: args.ExplicitNetwork.Seperator,
Fmt: args.ExplicitNetwork.Fmt,
},
AllowReserved: true,
AllowHostNet: true,
Network: args.ExplicitNetwork.Network,
}
cmnArgs = args.ExplicitNetwork.common
nets = make([]*netip.Prefix, 1)
nets[0] = new(netip.Prefix)
*nets[0] = origPfx.Masked()
@@ -91,14 +77,16 @@ func main() {
log.Panicln(err)
}
return
case "reserved":
// TODO
case "table":
// Account for a weird redundant CLI condition.
if args.Table.tableOpts.NoIpv4 && args.Table.tableOpts.NoIpv6 {
args.Table.tableOpts.NoIpv6 = false
args.Table.tableOpts.NoIpv4 = false
if args.Table.NoIpv4 && args.Table.NoIpv6 {
args.Table.NoIpv6 = false
args.Table.NoIpv4 = false
}
buf = new(bytes.Buffer)
if err = tblTpl.ExecuteTemplate(buf, "table.tpl", args.Table.tableOpts); err != nil {
if err = tblTpl.ExecuteTemplate(buf, "table.tpl", args.Table); err != nil {
log.Panicln(err)
}
os.Stdout.Write(buf.Bytes())
@@ -128,11 +116,7 @@ func main() {
origPfx = *resPfx
}
pfx = netipx.PrefixIPNet(origPfx.Masked())
cmnArgs = common{
outputOpts: args.Parse.outputOpts,
AllowReserved: args.Parse.AllowReserved,
AllowHostNet: args.Parse.AllowHostNet,
}
cmnArgs = args.Parse.common
if err = printNets(&origPfx, pfx, nets, remaining, &cmnArgs, res.GetSplitter()); err != nil {
log.Panicln(err)
}
@@ -191,6 +175,10 @@ func main() {
PrefixLengths: args.VLSM.Sizes,
BaseSplitter: new(netsplit.BaseSplitter),
}
default:
err = flags.ErrCommandRequired
log.Println(err)
os.Exit(1)
}
if origPfx, err = netip.ParsePrefix(cmnArgs.Network.Network); err != nil {
log.Panicln(err)

View File

@@ -4,20 +4,13 @@ import (
`math/big`
`net`
"net/netip"
`subnetter/netsplit`
)
// subnetResult is only used for human/"pretty" printing.
type subnetResult netip.Prefix
type tableOpts struct {
Notes bool `short:"n" long:"notes" description:"Include notes about prefixes (as a separate table)."`
Plain bool `short:"p" long:"plain" description:"Show plain table output."`
Legacy bool `short:"l" long:"legacy" description:"Include legacy/obsolete/deprecated information."`
NoV4Mask bool `short:"M" long:"no-mask" description:"Do not include netmasks for IPv4."`
NoIpv6 bool `short:"4" long:"ipv4" description:"Only show IPv4 table(s)."`
NoIpv4 bool `short:"6" long:"ipv6" description:"Only show IPv6 table(s)."`
}
// tableLegacy4 contains a spec for a class in the legacy "classed" IPv4 networking.
type tableLegacy4 struct {
Class string
@@ -102,3 +95,8 @@ type tableFormatter struct {
NoUpperTitle bool
NoBoldTitle bool
}
type ReservedResults struct {
Opts CheckArgs
Reserved map[netip.Prefix]*netsplit.IANAAddrNetResRecord
}