diff --git a/README.adoc b/README.adoc index 755367b..d486b71 100644 --- a/README.adoc +++ b/README.adoc @@ -15,6 +15,7 @@ Last rendered {localdatetime} :source-highlighter: rouge :docinfo: shared :rfc: https://datatracker.ietf.org/doc/html/rfc +:arin_r: https://www.arin.net/participate/policy/nrpm/ [id="wat"] == What is it? @@ -23,6 +24,10 @@ A tool to assist in design of segregate/segment/split/subnet networks. [id="out"] == Output +* `PTP` refers to "Peer-to-Peer" (e.g. {rfc}3021[RFC 3021^]). +* `6rd` refers to "IPv6 Rapid Deployment", a derivation of 6to4 ({rfc}5569[RFC 5569^], {rfc}5969[RFC 5969^]). +* `LIR` refers to "Local Internet Registry" ({arin_r}#2-4-local-internet-registry-lir[ARIN^]). +* `RIR` refers to "Regional Internet Registry" ({arin_r}#2-2-regional-internet-registry-rir[ARIN^]). * `Unicast` refers to "Global Unicast" ({rfc}1122[RFC 1122^], {rfc}4291#section-2.5.4[RFC 4291 ยง 2.5.4^], {rfc}4632[RFC 4632^]). ** For IPv6 addresses, it will be `true` for ULA (_Unique Local Addresses_) ({rfc}4193[RFC 4193^]) also. ** For IPv4 addresses, it will be `true` if the address is routable by external hosts (a unicast address), including private IP addresses ({rfc}1918[RFC 1918^]). diff --git a/cmd/subnetter/_tpl/table.tpl b/cmd/subnetter/_tpl/table.tpl index eaed57d..e273ebd 100644 --- a/cmd/subnetter/_tpl/table.tpl +++ b/cmd/subnetter/_tpl/table.tpl @@ -2,51 +2,73 @@ {{- $opts := . -}} {{- $numRows := 0 -}} {{- if not $opts.NoIpv4 }} -IPv4: - {{- if $opts.Legacy -}} - {{- $legacyspec := legacy4 }} - {{- $numRows = len $legacyspec.Rows }} - + {{- if $opts.Plain }} +IPV4: + {{- else }} +{{- bold "IPv4:"}} + {{- end }} + {{- if $opts.Legacy }} + {{- if $opts.Plain }} LEGACY: - {{ $legacyspec.Sizer.Hdr "" $opts.Plain }} - {{- range $idx, $row := $legacyspec.Rows }} - {{- $row.Row $legacyspec.Sizer "\t" $opts.Plain -}} - {{- $legacyspec.Sizer.Line "\t" $opts.Plain $idx $numRows }} + {{- else }} + {{ bold "Legacy:" }} {{- end }} + {{ legacy4 "\t" $opts.Plain }} {{- end }} {{- if not $opts.NoV4Mask }} - {{- $masks := mask4 }} + {{- if $opts.Plain }} NETMASKS: - {{ $masks.Sizer.Hdr "\t" $opts.Plain }} - {{- range $idx, $row := $masks.Rows }} - {{- $row.Row $masks.Sizer "\t" $opts.Plain }} - {{- $masks.Sizer.Line "\t" $opts.Plain $idx $numRows }} + {{- else }} + {{ bold "Netmasks:" }} {{- end }} + {{ mask4 "\t" $opts.Plain }} {{- end }} + {{- if $opts.Plain }} + CIDR: - {{- $pfxs := addrs 4 }} - {{- $numRows = len $pfxs.Rows }} - {{ $pfxs.Sizer.Hdr "" $opts.Plain }} - {{- range $idx, $row := $pfxs.Rows }} - {{- $row.Row $pfxs.Sizer "\t" $opts.Plain }} - {{- $pfxs.Sizer.Line "\t" $opts.Plain $idx $numRows }} - {{- end }} + {{- else }} + {{ bold "CIDR:" }} + {{- end }} + {{ prefixes 4 "\t" $opts.Plain }} + + {{- if $opts.Notes }} + {{- if $opts.Plain }} + + NOTES: + {{- else }} + + {{ bold "Notes:" }} + {{- end }} + {{ notes 4 "\t" $opts.Plain }} + {{- end }} {{- end }} {{- if not $opts.NoIpv6 }} -IPv6: - - CIDR: - {{- $pfxs := addrs 6 }} - {{- $numRows = len $pfxs.Rows }} - {{- $pfxs.Sizer.Hdr "\t" $opts.Plain }} - {{- range $idx, $row := $pfxs.Rows }} - {{- $row.Row $pfxs.Sizer "\t" $opts.Plain }} - {{- $pfxs.Sizer.Line "\t" $opts.Plain $idx $numRows }} - {{- end }} - +{{- if $opts.Plain }} +IPV6: +{{- else }} +{{ bold "IPv6:"}} +{{- end }} + {{- if $opts.Plain }} + CIDR: + {{- else }} + {{ bold "CIDR:" }} + {{- end }} + {{ prefixes 6 "\t" $opts.Plain }} + + + {{- if $opts.Notes }} + {{- if $opts.Plain }} + + NOTES: + {{- else }} + + {{ bold "Notes:" }} + {{- end }} + {{ notes 6 "\t" $opts.Plain }} + {{- end }} {{- end }} diff --git a/cmd/subnetter/args.go b/cmd/subnetter/args.go index 07946c2..d88384b 100644 --- a/cmd/subnetter/args.go +++ b/cmd/subnetter/args.go @@ -1,19 +1,21 @@ 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."` - 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"` - 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"` + 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."` + 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"` + 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"` } 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."` } @@ -51,8 +53,14 @@ type SplitSubnetArgs struct { type TableArgs struct { tableOpts - 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)."` - Net *string `short:"n" long:"network" description:"If specified, print detailed information explicitly about this network instead of reference. Ignores all other options except -v/--verbose." validate:"omitempty,cidr"` +} + +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"` } type VLSMArgs struct { @@ -62,5 +70,5 @@ type VLSMArgs struct { } type Net struct { - Network string `positional-arg-name:"/" description:"network address with prefix. Can be IPv4 or IPv6." validate:"required,cidr"` + Network string `positional-arg-name:"/" description:"Network address with prefix. Can be IPv4 or IPv6." validate:"required,cidr"` } diff --git a/cmd/subnetter/consts.go b/cmd/subnetter/consts.go index aae000c..38efdb0 100644 --- a/cmd/subnetter/consts.go +++ b/cmd/subnetter/consts.go @@ -31,9 +31,11 @@ var ( tplDir embed.FS tblTpl *template.Template = template.Must(template.New("").Funcs( template.FuncMap{ - "legacy4": tplClass4Iter, - "addrs": tplAddrIter, - "mask4": tplMaskIter4, + "bold": tplFmtBold, + "legacy4": tplTableLegacy4, + "mask4": tplTableMasks4, + "notes": tplTableNotes, + "prefixes": tplTablePrefixes, }, ).ParseFS(tplDir, "_tpl/*.tpl")) ) @@ -41,9 +43,19 @@ var ( var ( // Primarily output formatting stuff in this block. sectSepCnt = 48 - sectSep1 = strings.Repeat("=", sectSepCnt) - sectSep2 = strings.Repeat("-", sectSepCnt) - sectSep3 = strings.Repeat(".", sectSepCnt) + // sectFmts contains a lookup of map[][]string{, , } + sectFmts map[bool][]string = map[bool][]string{ + true: []string{ + strings.Repeat("=", sectSepCnt), + strings.Repeat("-", sectSepCnt), + strings.Repeat(".", sectSepCnt), + }, + false: []string{ + strings.Repeat("โ”", sectSepCnt), + strings.Repeat("โ”€", sectSepCnt), + strings.Repeat("โ•", sectSepCnt), + }, + } // tblFmts contains a lookup of map[]*tableFormatter. tblFmts map[bool]*tableFormatter = map[bool]*tableFormatter{ // Plaintext/ASCII-only @@ -101,3 +113,43 @@ var ( }, } ) + +var ( + // netNotes is keyed first on IP/inet family version (4/6) and then the prefix size. + netNotes map[uint8]map[uint8]string = map[uint8]map[uint8]string{ + 4: map[uint8]string{ + 32: "Host route/single host", + 31: "PTP link", + 30: "PTP (legacy/compatibility)", + 29: "Smallest multi-host network size possible", + 28: "Typical/common size for small LANs/VLANs", + 27: "Typical/common size for small LANs/VLANs", + 26: "Typical/common size for small LANs/VLANs", + 25: "Typical/common size for large LANs/VLANs", + 24: "Typical/common size for large LANs/VLANs", + 22: "Typical/common size for smaller business networks", + 21: "Typical/common size for larger business networks, smaller ISPs", + 20: "Typical/common size for larger business networks, smaller ISPs", + 19: "Typical/common size for enterprise business networks, larger ISPs", + 18: "Typical/common size for enterprise business networks, larger ISPs", + 17: "Typical/common size for enterprise business networks, larger ISPs", + 8: "Largest IANA block allocation size possible", + 0: "Entire IPv4 Internet address prefix; commonly used to indicate default route", + }, + 6: map[uint8]string{ + 128: "Host route/single host, single endpoints, and loopback (::1 explicitly)", + 127: "Point-to-Point link (inter-router)", + 64: "Single LAN; default prefix size for SLAAC", + 60: "Some (very limited) 6rd networks", + 56: "Minimum end site assignment (RFC 6177)", + 48: "Typical/common assignment for larger sites", + 36: "Possible future LIR \"extra-small\" allocation", + 32: "\"Minimal\" LIR allocation", + 28: "\"Medium\" LIR allocation", + 24: "\"Large\" LIR allocation", + 20: "\"Extra-large\" LIR allocation", + 12: "RIR allocation from IANA", + 0: "Entire IPv6 Internet address prefix; commonly used to represent default route", + }, + } +) diff --git a/cmd/subnetter/funcs.go b/cmd/subnetter/funcs.go index 24895b2..7ca7bed 100644 --- a/cmd/subnetter/funcs.go +++ b/cmd/subnetter/funcs.go @@ -219,6 +219,10 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem var invertedMask net.IPMask var res *netsplit.StructuredResults var verb = -1 + var fmts []string + var sectSep1 string + var sectSep2 string + // var sectSep3 string if orig == nil { return @@ -231,6 +235,10 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem }, } } + fmts = sectFmts[args.Plain] + sectSep1 = fmts[0] + sectSep2 = fmts[1] + // sectSep3 = fmts[2] if args.outputOpts.Verbose != nil { verb = 0 @@ -311,7 +319,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)) + fmt.Print(resFromPfx(n).pretty(verb, 1, args.outputOpts.Seperator, "\t", false, args.Plain)) } } if verb >= 1 { @@ -335,7 +343,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)) + fmt.Print(resFromPfx(&n).pretty(verb, 1, args.outputOpts.Seperator, "\t", true, args.Plain)) } } if verb >= 1 { diff --git a/cmd/subnetter/funcs_subnetresult.go b/cmd/subnetter/funcs_subnetresult.go index 1183a80..13a8574 100644 --- a/cmd/subnetter/funcs_subnetresult.go +++ b/cmd/subnetter/funcs_subnetresult.go @@ -6,13 +6,17 @@ import ( "strings" ) -func (s *subnetResult) pretty(verb, indent int, sep, indentStr string, isRemaining bool) (out string) { +func (s *subnetResult) pretty(verb, indent int, sep, indentStr string, isRemaining, plain bool) (out string) { var pfx netip.Prefix var bullet string = "+" var sb *strings.Builder = new(strings.Builder) var pre string = strings.Repeat(indentStr, indent) var pre2 string = strings.Repeat(indentStr, indent+1) + var fmts []string = sectFmts[plain] + // var sectSep1 string = fmts[0] + var sectSep2 string = fmts[1] + var sectSep3 string = fmts[2] if s == nil { return diff --git a/cmd/subnetter/funcs_tblrows.go b/cmd/subnetter/funcs_tblrows.go deleted file mode 100644 index 1a57d74..0000000 --- a/cmd/subnetter/funcs_tblrows.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - `reflect` -) - -// Row prints the formatted row for a tableAddr. -func (t *tableAddr) Row(sizer *tableAddrSizer, indent string, plain bool) (out string) { - - var val reflect.Value - var sizerVal reflect.Value - - if t == nil || sizer == nil { - return - } - val = reflect.ValueOf(*t) - sizerVal = reflect.ValueOf(*sizer) - - out = rowRender(val, sizerVal, indent, plain) - return -} - -// Row prints the formatted row for a tableLegacy4. -func (t *tableLegacy4) Row(sizer *tableLegacy4Sizer, indent string, plain bool) (out string) { - - var val reflect.Value - var sizerVal reflect.Value - - if t == nil || sizer == nil { - return - } - val = reflect.ValueOf(*t) - sizerVal = reflect.ValueOf(*sizer) - - out = rowRender(val, sizerVal, indent, plain) - return -} - -// Row prints the formatted row for a tableMask4. -func (t *tableMask4) Row(sizer *tableMask4Sizer, indent string, plain bool) (out string) { - - var val reflect.Value - var sizerVal reflect.Value - - if t == nil || sizer == nil { - return - } - val = reflect.ValueOf(*t) - sizerVal = reflect.ValueOf(*sizer) - - out = rowRender(val, sizerVal, indent, plain) - - return -} diff --git a/cmd/subnetter/funcs_tblsizers.go b/cmd/subnetter/funcs_tblsizers.go deleted file mode 100644 index b606abd..0000000 --- a/cmd/subnetter/funcs_tblsizers.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - `reflect` -) - -/* - Hdr prints the header for a tableAddrSizer corresponding to a slice of tableAddr. - - indent will be printed before the string. - - If plain is true, only ASCII chars will be used; otherwise fancy-schmancy Unicode. -*/ -func (t *tableAddrSizer) Hdr(indent string, plain bool) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrRender(val, indent, plain) - - return -} - -// Line either prints the *last line* (the border) or a *row separator* (if allowed in the format). -func (t *tableAddrSizer) Line(indent string, plain bool, rowIdx, numRows int) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrLineRender(val, indent, plain, rowIdx, numRows) - - return -} - -/* - Hdr prints the header for a tableLegacy4Sizer corresponding to a slice of tableLegacy4. - - indent will be printed before the string. - - If plain is true, only ASCII chars will be used; otherwise fancy-schmancy Unicode. -*/ -func (t *tableLegacy4Sizer) Hdr(indent string, plain bool) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrRender(val, indent, plain) - - return -} - -// Line either prints the *last line* (the border) or a *row separator* (if allowed in the format). -func (t *tableLegacy4Sizer) Line(indent string, plain bool, rowIdx, numRows int) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrLineRender(val, indent, plain, rowIdx, numRows) - - return -} - -/* - Hdr prints the header for a tableMask4Sizer corresponding to a slice of tableMask4. - - indent will be printed before the string. - - If plain is true, only ASCII chars will be used; otherwise fancy-schmancy Unicode. -*/ -func (t *tableMask4Sizer) Hdr(indent string, plain bool) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrRender(val, indent, plain) - - return -} - -// Line either prints the *last line* (the border) or a *row separator* (if allowed in the format). -func (t *tableMask4Sizer) Line(indent string, plain bool, rowIdx, numRows int) (out string) { - - var val reflect.Value - - if t == nil { - return - } - val = reflect.ValueOf(*t) - - out = hdrLineRender(val, indent, plain, rowIdx, numRows) - - return -} diff --git a/cmd/subnetter/funcs_tpl.go b/cmd/subnetter/funcs_tpl.go index b79e7d0..18b0988 100644 --- a/cmd/subnetter/funcs_tpl.go +++ b/cmd/subnetter/funcs_tpl.go @@ -15,20 +15,187 @@ import ( `subnetter/netsplit` ) -/* - tplClass4Iter should only be called if legacy info is enabled. - It returns a tableLegacy4Sizer and a slice of tableLegacy4. +// renderHdr renders a header. Note that the first line does *not* include the indent, but subsequent ones do. +func renderHdr(titles []string, colSizes []uint8, indent string, plain bool, sb *strings.Builder) { - It takes no input. + var idx int + var width uint8 + var title string + var lastTitleIdx int + var tfmt *tableFormatter = tblFmts[plain] + + if titles == nil || len(titles) == 0 || colSizes == nil || len(colSizes) == 0 { + return + } + + lastTitleIdx = len(titles) - 1 + + // Top-most line/border. + sb.WriteString(tfmt.TopLeftHdr) + for idx, width = range colSizes { + sb.WriteString(strings.Repeat(tfmt.TopFillHdr, int(width))) + if idx == lastTitleIdx { + sb.WriteString(tfmt.TopRightHdr) + } else { + sb.WriteString(tfmt.TopColSepHdr) + } + } + sb.WriteString("\n") + + // Title names. + sb.WriteString(indent) + sb.WriteString(tfmt.Left) + for idx, title = range titles { + width = colSizes[idx] + if !tfmt.NoUpperTitle { + title = strings.ToUpper(title) + } + if tfmt.NoBoldTitle { + sb.WriteString(padStr(title, width)) + } else { + sb.WriteString(color.InBold(padStr(title, width))) + } + if idx == lastTitleIdx { + sb.WriteString(tfmt.Right) + } else { + sb.WriteString(tfmt.ColSepHdr) + } + } + sb.WriteString("\n") + + // Bottom header border. + sb.WriteString(indent) + sb.WriteString(tfmt.BottomLeftHdr) + for idx, width = range colSizes { + sb.WriteString(strings.Repeat(tfmt.BottomFillHdr, int(width))) + if idx == lastTitleIdx { + sb.WriteString(tfmt.BottomRightHdr) + } else { + sb.WriteString(tfmt.BottomColSepHdr) + } + } + sb.WriteString("\n") + + return +} + +// renderRow renders a row. row should be a struct or non-nil struct ptr. +func renderRow(row reflect.Value, colFields []int, colSizes []uint8, indent string, plain, isLast bool, sb *strings.Builder) { + + var idx int + var width uint8 + var fieldIdx int + var valStr string + var lastColIdx int + var field reflect.Value + var tfmt *tableFormatter = tblFmts[plain] + + if colFields == nil || len(colFields) == 0 || colSizes == nil || len(colSizes) == 0 { + return + } + if row.Kind() == reflect.Ptr && (row.IsNil() || row.Kind() != reflect.Struct) { + return + } + row = reflect.Indirect(row) + if row.Kind() != reflect.Struct { + return + } + + lastColIdx = len(colFields) - 1 + + sb.WriteString(indent) + sb.WriteString(tfmt.Left) + for idx, fieldIdx = range colFields { + width = colSizes[idx] + field = row.Field(fieldIdx) + switch field.Kind() { + case reflect.String: + valStr = field.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + valStr = strconv.FormatInt(field.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + valStr = strconv.FormatUint(field.Uint(), 10) + case reflect.Ptr: + // *big.Int + if field.IsNil() { + continue + } + field = field.MethodByName("String").Call(nil)[0] + valStr = field.String() + } + sb.WriteString(padStr(valStr, width)) + if idx == lastColIdx { + sb.WriteString(tfmt.Right) + } else { + sb.WriteString(tfmt.ColSep) + } + } + sb.WriteString("\n") + + if !tfmt.SuppressLineSep || isLast { + sb.WriteString(indent) + if isLast { + sb.WriteString(tfmt.LastLeft) + } else { + sb.WriteString(tfmt.LineLeft) + } + for idx, width = range colSizes { + if isLast { + sb.WriteString(strings.Repeat(tfmt.LastFill, int(width))) + } else { + sb.WriteString(strings.Repeat(tfmt.Fill, int(width))) + } + if idx == lastColIdx { + if isLast { + sb.WriteString(tfmt.LastRight) + } else { + sb.WriteString(tfmt.LineRight) + } + } else { + if isLast { + sb.WriteString(tfmt.LastSep) + } else { + sb.WriteString(tfmt.LineColSep) + } + } + } + sb.WriteString("\n") + } + + return +} + +// tplFmtBold renders text as bold-faced. +func tplFmtBold(text string) (out string) { + + out = color.InBold(text) + + return +} + +/* + tplTableLegacy4 renders a table of classes in the legacy "classed" IPv4 networking. + + Note that indent is *not* applied to the first line. */ -func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) { +func tplTableLegacy4(indent string, plain bool) (out string, err error) { // This whole thing feels dirty. // It's like adding a microcontroller to a rock. // But it works. + var idx int + var cls string + var isLast bool + var colFields []int + var colSizes []uint8 + var colTitles []string var pfx *net.IPNet + var row tableLegacy4 + var rows []tableLegacy4 + var rowVal reflect.Value var classNets []*netip.Prefix var netRange netipx.IPRange + var sb *strings.Builder = new(strings.Builder) var v *netsplit.VLSMSplitter = &netsplit.VLSMSplitter{ Ascending: false, PrefixLengths: []uint8{ @@ -48,53 +215,124 @@ func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) { if classNets, _, err = v.Split(); err != nil { return } + rows = make([]tableLegacy4, 5) - legacySpec = &tableLegacy4Ret{ - Sizer: &tableLegacy4Sizer{ - Class: 5, // "CLASS" - CIDR: 4, // "BITS" - Start: 5, // "START" - End: 3, // "END" - }, - Rows: make([]tableLegacy4, 5), - } - for idx, cls := range []string{ + for idx, cls = range []string{ "A", "B", "C", "D", "E", } { - legacySpec.Rows[idx] = tableLegacy4{ + rows[idx] = tableLegacy4{ Class: cls, CIDR: classNets[idx].String(), NetCIDR: *classNets[idx], } - netRange = netipx.RangeOfPrefix(legacySpec.Rows[idx].NetCIDR) - legacySpec.Rows[idx].NetStart = netRange.From() - legacySpec.Rows[idx].NetEnd = netRange.To() - legacySpec.Rows[idx].Start = legacySpec.Rows[idx].NetStart.String() - legacySpec.Rows[idx].End = legacySpec.Rows[idx].NetEnd.String() + netRange = netipx.RangeOfPrefix(rows[idx].NetCIDR) + rows[idx].NetStart = netRange.From() + rows[idx].NetEnd = netRange.To() + rows[idx].Start = rows[idx].NetStart.String() + rows[idx].End = rows[idx].NetEnd.String() } + colFields, colTitles, colSizes = sizeStructs(rows) + + // Header + renderHdr(colTitles, colSizes, indent, plain, sb) + + // Rows + for idx, row = range rows { + rowVal = reflect.ValueOf(row) + isLast = idx == len(rows)-1 + renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb) + } + + out = sb.String() + return } /* - tplAddrIter takes a 4 or 6 for inet family/version and returns a tableAddrSizer and - slice of tableAddr. - tableAddr is sorted from smallest prefix/largest network to largest prefix/smallest network. + tplTableMasks4 renders a table of netmasks for IPv4. + IPv6 doesn't really use netmasks, instead relying almost entirely upon CIDR. + + Note that indent is *not* applied to the first line. */ -func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) { +func tplTableMasks4(indent string, plain bool) (out string, err error) { - var dummyAddr netip.Addr + var idx int + var isLast bool + var colFields []int + var colSizes []uint8 + var colTitles []string + var row tableMask4 + var rows []tableMask4 var dummyNet *net.IPNet - var l int + var dummyAddr netip.Addr + var rowVal reflect.Value + var sb *strings.Builder = new(strings.Builder) - addrs = &tableAddrRet{ - Sizer: &tableAddrSizer{ - Prefix: 6, // "PREFIX" - Bits: 4, // "BITS" - Addresses: 9, // "ADDRESSES" - Hosts: 5, // "HOSTS" - }, + if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil { + return } + rows = make([]tableMask4, dummyAddr.BitLen()+1) + + for idx = 0; idx <= dummyAddr.BitLen(); idx++ { + rows[idx] = tableMask4{ + Prefix: fmt.Sprintf("/%d", idx), + } + if rows[idx].NetPrefix, err = dummyAddr.Prefix(idx); err != nil { + return + } + dummyNet = netipx.PrefixIPNet(rows[idx].NetPrefix.Masked()) + rows[idx].Mask = dummyNet.Mask + rows[idx].Netmask = netsplit.MaskFmt( + dummyNet.Mask, + "d", ".", "", + 1, 0, + ) + rows[idx].Hex = dummyNet.Mask.String() + rows[idx].Dec = binary.BigEndian.Uint32(dummyNet.Mask) + rows[idx].Bin = netsplit.MaskFmt( + dummyNet.Mask, + "08b", ".", "", + 1, 0, + ) + } + + colFields, colTitles, colSizes = sizeStructs(rows) + + // Header + renderHdr(colTitles, colSizes, indent, plain, sb) + + // Rows + for idx, row = range rows { + rowVal = reflect.ValueOf(row) + isLast = idx == len(rows)-1 + renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb) + } + + out = sb.String() + + return +} + +/* + tplTableNotes renders a table of prefix notes IP version/inet family ipVer (4 or 6). + + Note that indent is *not* applied to the first line. +*/ +func tplTableNotes(ipVer uint8, indent string, plain bool) (out string, err error) { + + var idx int + var isLast bool + var ok bool + var note string + var colFields []int + var colSizes []uint8 + var colTitles []string + var row tablePrefixNote + var rows []tablePrefixNote + var dummyAddr netip.Addr + var rowVal reflect.Value + var sb *strings.Builder = new(strings.Builder) switch ipVer { case 4: @@ -109,339 +347,185 @@ func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) { err = errBadNet return } + rows = make([]tablePrefixNote, 0, dummyAddr.BitLen()+1) - // Before we size, we generate the tableAddrs. - addrs.Rows = make([]tableAddr, dummyAddr.BitLen()+1) - for i := 0; i <= dummyAddr.BitLen(); i++ { - addrs.Rows[i] = tableAddr{ - Prefix: uint8(i), - Bits: uint8(dummyAddr.BitLen() - i), + for idx = 0; idx <= dummyAddr.BitLen(); idx++ { + if note, ok = netNotes[ipVer][uint8(idx)]; !ok { + continue } - if addrs.Rows[i].NetPrefix, err = dummyAddr.Prefix(i); err != nil { + row = tablePrefixNote{ + Prefix: fmt.Sprintf("/%d", idx), + Note: note, + } + if row.NetPrefix, err = dummyAddr.Prefix(idx); err != nil { return } - dummyNet = netipx.PrefixIPNet(addrs.Rows[i].NetPrefix.Masked()) - addrs.Rows[i].Addresses = mapcidr.CountIPsInCIDR(true, true, dummyNet) - addrs.Rows[i].Hosts = mapcidr.CountIPsInCIDR(false, false, dummyNet) + rows = append(rows, row) } - // Now the sizer. The padding itself is handled in different logic, just need the length of the longest value as a string. - for _, addr := range addrs.Rows { - // I *abhor* walrus operators in anything but loops. - l = len(strconv.Itoa(int(addr.Prefix))) - if int(addrs.Sizer.Prefix) < l { - addrs.Sizer.Prefix = uint8(l) - } - l = len(strconv.Itoa(int(addr.Bits))) - if int(addrs.Sizer.Bits) < l { - addrs.Sizer.Bits = uint8(l) - } - // Use the full numeric length. - l = len(addr.Addresses.String()) - if int(addrs.Sizer.Addresses) < l { - addrs.Sizer.Addresses = uint8(l) - } - l = len(addr.Hosts.String()) - if int(addrs.Sizer.Hosts) < l { - addrs.Sizer.Hosts = uint8(l) - } + colFields, colTitles, colSizes = sizeStructs(rows) + + // Header + renderHdr(colTitles, colSizes, indent, plain, sb) + + // Rows + for idx, row = range rows { + rowVal = reflect.ValueOf(row) + isLast = idx == len(rows)-1 + renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb) } + out = sb.String() + return } /* - tplMaskIter4 returns a slice of IPv4 netmasks and returns a slice of tableMask4. - Sorted from smallest prefix/largest network to largest prefix/smallest network. + tplTablePrefixes renders a table of prefixes for IP version/inet family ipVer (4 or 6). + + Note that indent is *not* applied to the first line. */ -func tplMaskIter4() (masks *tableMask4Ret, err error) { +func tplTablePrefixes(ipVer uint8, indent string, plain bool) (out string, err error) { - var dummyAddr netip.Addr - var pfx netip.Prefix + var idx int + var isLast bool + var colFields []int + var colSizes []uint8 + var colTitles []string + var row tablePrefix + var rows []tablePrefix var dummyNet *net.IPNet - var l int + var dummyAddr netip.Addr + var rowVal reflect.Value + var sb *strings.Builder = new(strings.Builder) - masks = &tableMask4Ret{ - Sizer: &tableMask4Sizer{ - Prefix: 6, // "PREFIX" - Netmask: 7, // "NETMASK" - Hex: 3, // "HEX" - Dec: 3, // "DEC" - Bin: 3, // "BIN" - }, - } - - if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil { - return - } - - masks.Rows = make([]tableMask4, dummyAddr.BitLen()+1) - for i := 0; i <= dummyAddr.BitLen(); i++ { - if pfx, err = dummyAddr.Prefix(i); err != nil { + switch ipVer { + case 4: + if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil { return } - dummyNet = netipx.PrefixIPNet(pfx.Masked()) - masks.Rows[i] = tableMask4{ - Prefix: uint8(i), - Netmask: netsplit.MaskFmt( - dummyNet.Mask, - "d", ".", "", - 1, 0, - ), - Hex: dummyNet.Mask.String(), - Dec: binary.BigEndian.Uint32(dummyNet.Mask), - Bin: netsplit.MaskFmt( - dummyNet.Mask, - "08b", ".", "", - 1, 0, - ), - Mask: dummyNet.Mask, + case 6: + if dummyAddr, err = netip.ParseAddr("::"); err != nil { + return } - } - - // Now the sizer. - for _, mask := range masks.Rows { - l = len(strconv.Itoa(int(mask.Prefix))) - if int(masks.Sizer.Prefix) < l { - masks.Sizer.Prefix = uint8(l) - } - l = len(mask.Netmask) - if int(masks.Sizer.Netmask) < l { - masks.Sizer.Netmask = uint8(l) - } - l = len(mask.Hex) - if int(masks.Sizer.Hex) < l { - masks.Sizer.Hex = uint8(l) - } - l = len(strconv.FormatUint(uint64(mask.Dec), 10)) - if int(masks.Sizer.Dec) < l { - masks.Sizer.Dec = uint8(l) - } - l = len(mask.Bin) - if int(masks.Sizer.Bin) < l { - masks.Sizer.Bin = uint8(l) - } - } - - return -} - -// do not include in template funcs; used externally -func hdrRender(hdrVal reflect.Value, indent string, plain bool) (out string) { - - var val reflect.Value - var field reflect.StructField - var fieldVal reflect.Value - var colLen uint8 - var colTitle string - var lastField int - var valType reflect.Type - var tfmt *tableFormatter = tblFmts[plain] - var sb *strings.Builder = new(strings.Builder) - - val = hdrVal - valType = val.Type() - - // Avoid the edge case where a struct's last field is skipped rendering - for i := val.NumField(); i > 0; i-- { - field = valType.Field(i - 1) - if field.Tag.Get("render") == "-" { - continue - } - lastField = i - break - } - - // Top-most line. - sb.WriteString(indent) - sb.WriteString(tfmt.TopLeftHdr) - for i := 0; i < val.NumField(); i++ { - field = valType.Field(i) - if field.Tag.Get("render") == "-" { - continue - } - fieldVal = val.Field(i) - colLen = uint8(fieldVal.Uint()) - sb.WriteString(strings.Repeat(tfmt.TopFillHdr, int(colLen)+fixedPad)) - if i == lastField { - sb.WriteString(tfmt.TopRightHdr) - } else { - sb.WriteString(tfmt.TopColSepHdr) - } - } - sb.WriteString("\n") - - // Column titles - sb.WriteString(indent) - sb.WriteString(tfmt.Left) - for i := 0; i < val.NumField(); i++ { - field = valType.Field(i) - if field.Tag.Get("render") == "-" { - continue - } - fieldVal = val.Field(i) - colLen = uint8(fieldVal.Uint()) + uint8(fixedPad) - colTitle = field.Name - if !tfmt.NoUpperTitle { - colTitle = strings.ToUpper(colTitle) - } - if !tfmt.NoBoldTitle { - sb.WriteString(color.InBold(padStr(colTitle, colLen))) - } else { - sb.WriteString(padStr(colTitle, colLen)) - } - if i == lastField { - sb.WriteString(tfmt.Right) - } else { - sb.WriteString(tfmt.ColSepHdr) - } - } - sb.WriteString("\n") - - // Header bottom line; headers always include bottom separators. - sb.WriteString(indent) - sb.WriteString(tfmt.BottomLeftHdr) - for i := 0; i < val.NumField(); i++ { - field = valType.Field(i) - if field.Tag.Get("render") == "-" { - continue - } - fieldVal = val.Field(i) - colLen = uint8(fieldVal.Uint()) - sb.WriteString(strings.Repeat(tfmt.BottomFillHdr, int(colLen)+fixedPad)) - if i == lastField { - sb.WriteString(tfmt.BottomRightHdr) - } else { - sb.WriteString(tfmt.BottomColSepHdr) - } - } - sb.WriteString("\n") - - out = sb.String() - - return -} - -// do not include in template funcs; used externally -func hdrLineRender(hdrVal reflect.Value, indent string, plain bool, rowIdx int, numRows int) (out string) { - - var val reflect.Value - var field reflect.StructField - var fieldVal reflect.Value - var colLen uint8 - var lastField int - var isLastLine bool - var valType reflect.Type - var tfmt *tableFormatter = tblFmts[plain] - var sb *strings.Builder = new(strings.Builder) - - isLastLine = rowIdx == (numRows - 1) - if !isLastLine && tfmt.SuppressLineSep { + default: + err = errBadNet return } + rows = make([]tablePrefix, dummyAddr.BitLen()+1) - val = hdrVal - valType = val.Type() - lastField = valType.NumField() - 1 - - for i := val.NumField(); i >= 0; i-- { - field = valType.Field(i - 1) - if field.Tag.Get("render") == "-" { - continue + for idx = 0; idx <= dummyAddr.BitLen(); idx++ { + rows[idx] = tablePrefix{ + Prefix: fmt.Sprintf("/%d", idx), + Bits: uint8(dummyAddr.BitLen() - idx), } - lastField = i - break + if rows[idx].NetPrefix, err = dummyAddr.Prefix(idx); err != nil { + return + } + dummyNet = netipx.PrefixIPNet(rows[idx].NetPrefix.Masked()) + rows[idx].Addresses = mapcidr.CountIPsInCIDR(true, true, dummyNet) + rows[idx].Hosts = mapcidr.CountIPsInCIDR(false, false, dummyNet) } - sb.WriteString(indent) - if isLastLine { - sb.WriteString(tfmt.LastLeft) - } else { - sb.WriteString(tfmt.LineLeft) + colFields, colTitles, colSizes = sizeStructs(rows) + + // Header + renderHdr(colTitles, colSizes, indent, plain, sb) + + // Rows + for idx, row = range rows { + rowVal = reflect.ValueOf(row) + isLast = idx == len(rows)-1 + renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb) } - for i := 0; i < val.NumField(); i++ { - field = valType.Field(i) - if field.Tag.Get("render") == "-" { - continue - } - fieldVal = val.Field(i) - colLen = uint8(fieldVal.Uint()) - if isLastLine { - sb.WriteString(strings.Repeat(tfmt.LastFill, int(colLen)+fixedPad)) - } else { - sb.WriteString(strings.Repeat(tfmt.Fill, int(colLen)+fixedPad)) - } - if i == lastField { - if isLastLine { - sb.WriteString(tfmt.LastRight) - } else { - sb.WriteString(tfmt.LineRight) - } - } else { - if isLastLine { - sb.WriteString(tfmt.LastSep) - } else { - sb.WriteString(tfmt.LineColSep) - } - } - } - sb.WriteString("\n") out = sb.String() return } -// do not include in template funcs; used externally -func rowRender(val reflect.Value, sizerVal reflect.Value, indent string, plain bool) (out string) { +// rows should be a slice of structs/struct ptrs of all the same type (you're going to get errors/panics otherwise). +func sizeStructs(rows any) (colFieldIdx []int, colTitles []string, colSizes []uint8) { + var title string + var size int + var numFields int + var colIdx int + var fieldIdx int + var valLen int + var valStr string + var rowVals []reflect.Value + var val reflect.Value + var valType reflect.Type var field reflect.StructField var fieldVal reflect.Value - var colLen uint8 - var sizerName string - var sizerField reflect.Value - var callVal string - var valType reflect.Type = val.Type() - var tfmt *tableFormatter = tblFmts[plain] - var sb *strings.Builder = new(strings.Builder) - sb.WriteString(indent) - for i := 0; i < val.NumField(); i++ { + // Here be dragons. This is *NOT SAFE* for generic (heh) consumption. + val = reflect.ValueOf(rows) + if val.IsNil() || val.Type().Kind() != reflect.Slice || val.Len() == 0 { + return + } + rowVals = make([]reflect.Value, val.Len()) + for idx := 0; idx < val.Len(); idx++ { + rowVals[idx] = val.Index(idx) + } + + // I *think* this could potentially cause a panic if rows[0] is nil, BUT this func can't be used externally so it's Fine(TM). + // Usage of it is tightly scoped. + valType = reflect.Indirect(rowVals[0]).Type() + if valType.Kind() != reflect.Struct { + return + } + numFields = reflect.Indirect(rowVals[0]).NumField() + colTitles = make([]string, 0, numFields) + colSizes = make([]uint8, 0, numFields) + colFieldIdx = make([]int, 0, numFields) + + // Populate from the struct type first. + for i := 0; i < numFields; i++ { field = valType.Field(i) if field.Tag.Get("render") == "-" { continue } - sb.WriteString(tfmt.Left) - fieldVal = val.Field(i) - sizerName = field.Tag.Get("renderSizeName") - if sizerName == "" { - sizerName = field.Name + title = field.Tag.Get("renderTitle") + if title == "" { + title = field.Name } - sizerField = sizerVal.FieldByName(sizerName) - colLen = uint8(sizerField.Uint()) + uint8(fixedPad) - switch fieldVal.Kind() { - // This is tailored specifically to this implementation. - case reflect.String: - sb.WriteString(fieldVal.String()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Int()), colLen)) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Uint()), colLen)) - case reflect.Ptr: - // It's a *big.Int. - if fieldVal.IsNil() { - sb.WriteString(padStr(strings.Repeat(padChars, int(colLen)), colLen)) - } else { - // TIL you can even *do* this in reflection. + size = len(title) + (fixedPad * len(padChars)) + colTitles = append(colTitles, title) + colSizes = append(colSizes, uint8(size)) + colFieldIdx = append(colFieldIdx, i) + } + // colTitles is done. + + // check each field in each row to see if its value's size is greater than the current. + for _, row := range rowVals { + val = reflect.Indirect(row) + if val.Kind() != reflect.Struct { + continue + } + for colIdx, fieldIdx = range colFieldIdx { + fieldVal = val.Field(fieldIdx) + // The row struct types only implement these primitives. + switch fieldVal.Kind() { + case reflect.String: + valStr = fieldVal.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + valStr = strconv.FormatInt(fieldVal.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + valStr = strconv.FormatUint(fieldVal.Uint(), 10) + case reflect.Ptr: + // *big.Int. No need to/don't fieldVal.Indirect(); .String() is a ptr method. + // Sidenote, I didn't even know until today that reflection can *do* this. fieldVal = fieldVal.MethodByName("String").Call(nil)[0] - callVal = fieldVal.String() - sb.WriteString(padStr(callVal, colLen)) + valStr = fieldVal.String() + } + valLen = len(valStr) + (fixedPad * len(padChars)) + if valLen > int(colSizes[colIdx]) { + colSizes[colIdx] = uint8(valLen) } } } - sb.WriteString("\n") - - out = sb.String() return } diff --git a/cmd/subnetter/main.go b/cmd/subnetter/main.go index b8a29c1..acf7c9f 100644 --- a/cmd/subnetter/main.go +++ b/cmd/subnetter/main.go @@ -67,7 +67,36 @@ func main() { } 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, + } + nets = make([]*netip.Prefix, 1) + nets[0] = new(netip.Prefix) + *nets[0] = origPfx.Masked() + if err = printNets(&origPfx, pfx, nets, nil, &cmnArgs, nil); err != nil { + log.Panicln(err) + } + return 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 + } buf = new(bytes.Buffer) if err = tblTpl.ExecuteTemplate(buf, "table.tpl", args.Table.tableOpts); err != nil { log.Panicln(err) diff --git a/cmd/subnetter/types.go b/cmd/subnetter/types.go index 0ca8001..f9cf3ca 100644 --- a/cmd/subnetter/types.go +++ b/cmd/subnetter/types.go @@ -10,6 +10,7 @@ import ( 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."` @@ -17,56 +18,6 @@ type tableOpts struct { NoIpv4 bool `short:"6" long:"ipv6" description:"Only show IPv6 table(s)."` } -type tableAddrRet struct { - Sizer *tableAddrSizer - Rows []tableAddr -} - -type tableAddr struct { - Prefix uint8 - Bits uint8 - Addresses *big.Int - Hosts *big.Int - NetPrefix netip.Prefix `render:"-"` -} - -// tableAddrSizer is used to control spacing/sizing of a tableAddr table's columns. -type tableAddrSizer struct { - Prefix uint8 - Bits uint8 - Addresses uint8 - Hosts uint8 -} - -type tableMask4Ret struct { - Sizer *tableMask4Sizer - Rows []tableMask4 -} - -// tableMask4 is used to hold string representation of netmask information. -type tableMask4 struct { - Prefix uint8 - Netmask string - Hex string - Dec uint32 - Bin string - Mask net.IPMask `render:"-"` -} - -// tableMask4Sizer, like tableAddrSizer, is used to control spacing/sizing of a tableMask4 table's columns. -type tableMask4Sizer struct { - Prefix uint8 - Netmask uint8 - Hex uint8 - Dec uint8 - Bin uint8 -} - -type tableLegacy4Ret struct { - Sizer *tableLegacy4Sizer - Rows []tableLegacy4 -} - // tableLegacy4 contains a spec for a class in the legacy "classed" IPv4 networking. type tableLegacy4 struct { Class string @@ -78,12 +29,31 @@ type tableLegacy4 struct { NetCIDR netip.Prefix `render:"-"` } -// tableLegacy4Sizer is used to size tableLegacy4 entries. -type tableLegacy4Sizer struct { - Class uint8 - CIDR uint8 - Start uint8 - End uint8 +// tableMask4 is used to hold string representation of netmask information. +type tableMask4 struct { + Prefix string + Netmask string + Hex string + Dec uint32 + Bin string + Mask net.IPMask `render:"-"` + NetPrefix netip.Prefix `render:"-"` +} + +// tablePrefixNote is used to hold notes about select prefix sizes. +type tablePrefixNote struct { + Prefix string + Note string + NetPrefix netip.Prefix `render:"-"` +} + +// tablePrefix is used to hold string representation of network prefixes. +type tablePrefix struct { + Prefix string + Bits uint8 + Addresses *big.Int + Hosts *big.Int + NetPrefix netip.Prefix `render:"-"` } // tableFormatter is used for "rendering" table output. diff --git a/go.mod b/go.mod index 185f214..1caec39 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.23.5 require ( github.com/TwiN/go-color v1.4.1 + github.com/davecgh/go-spew v1.1.1 github.com/go-playground/validator/v10 v10.24.0 github.com/goccy/go-yaml v1.15.16 github.com/jessevdk/go-flags v1.6.1 diff --git a/netsplit/conts.go b/netsplit/conts.go new file mode 100644 index 0000000..e578393 --- /dev/null +++ b/netsplit/conts.go @@ -0,0 +1,12 @@ +package netsplit + +import ( + `net/netip` +) + +var ( + ReservedNets map[netip.Prefix]string + reservedNetsOrig map[string]string = map[string]string{ + "": "", + } +) diff --git a/netsplit/init.go b/netsplit/init.go index c02b75c..36dbc27 100644 --- a/netsplit/init.go +++ b/netsplit/init.go @@ -1,3 +1,20 @@ package netsplit -// TODO? +import ( + `log` + `net/netip` +) + +func init() { + var err error + var pfx netip.Prefix + + ReservedNets = make(map[netip.Prefix]string) + + for np, reason := range reservedNetsOrig { + if pfx, err = netip.ParsePrefix(np); err != nil { + log.Panicln(err) + } + ReservedNets[pfx] = reason + } +}