just needs reserved prefix warnings implemented

This commit is contained in:
Brent S. 2025-02-02 03:45:29 -05:00
parent 3a7ed5973b
commit 30355294c0
Signed by: bts.work
GPG Key ID: 004FD489E0203EEE
14 changed files with 643 additions and 598 deletions

View File

@ -15,6 +15,7 @@ Last rendered {localdatetime}
:source-highlighter: rouge :source-highlighter: rouge
:docinfo: shared :docinfo: shared
:rfc: https://datatracker.ietf.org/doc/html/rfc :rfc: https://datatracker.ietf.org/doc/html/rfc
:arin_r: https://www.arin.net/participate/policy/nrpm/


[id="wat"] [id="wat"]
== What is it? == What is it?
@ -23,6 +24,10 @@ A tool to assist in design of segregate/segment/split/subnet networks.
[id="out"] [id="out"]
== Output == 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^]). * `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 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^]). ** 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^]).

View File

@ -2,51 +2,73 @@
{{- $opts := . -}} {{- $opts := . -}}
{{- $numRows := 0 -}} {{- $numRows := 0 -}}
{{- if not $opts.NoIpv4 }} {{- if not $opts.NoIpv4 }}
IPv4: {{- if $opts.Plain }}
{{- if $opts.Legacy -}} IPV4:
{{- $legacyspec := legacy4 }} {{- else }}
{{- $numRows = len $legacyspec.Rows }} {{- bold "IPv4:"}}

{{- end }}
{{- if $opts.Legacy }}
{{- if $opts.Plain }}
LEGACY: LEGACY:
{{ $legacyspec.Sizer.Hdr "" $opts.Plain }} {{- else }}
{{- range $idx, $row := $legacyspec.Rows }} {{ bold "Legacy:" }}
{{- $row.Row $legacyspec.Sizer "\t" $opts.Plain -}}
{{- $legacyspec.Sizer.Line "\t" $opts.Plain $idx $numRows }}
{{- end }} {{- end }}
{{ legacy4 "\t" $opts.Plain }}
{{- end }} {{- end }}


{{- if not $opts.NoV4Mask }} {{- if not $opts.NoV4Mask }}
{{- $masks := mask4 }} {{- if $opts.Plain }}
NETMASKS: NETMASKS:
{{ $masks.Sizer.Hdr "\t" $opts.Plain }} {{- else }}
{{- range $idx, $row := $masks.Rows }} {{ bold "Netmasks:" }}
{{- $row.Row $masks.Sizer "\t" $opts.Plain }}
{{- $masks.Sizer.Line "\t" $opts.Plain $idx $numRows }}
{{- end }} {{- end }}
{{ mask4 "\t" $opts.Plain }}
{{- end }} {{- end }}


{{- if $opts.Plain }}

CIDR: CIDR:
{{- $pfxs := addrs 4 }} {{- else }}
{{- $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 }}


{{ 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 }} {{- end }}


{{- if not $opts.NoIpv6 }} {{- if not $opts.NoIpv6 }}


IPv6: {{- if $opts.Plain }}

IPV6:
CIDR: {{- else }}
{{- $pfxs := addrs 6 }} {{ bold "IPv6:"}}
{{- $numRows = len $pfxs.Rows }} {{- end }}
{{- $pfxs.Sizer.Hdr "\t" $opts.Plain }} {{- if $opts.Plain }}
{{- range $idx, $row := $pfxs.Rows }} CIDR:
{{- $row.Row $pfxs.Sizer "\t" $opts.Plain }} {{- else }}
{{- $pfxs.Sizer.Line "\t" $opts.Plain $idx $numRows }} {{ bold "CIDR:" }}
{{- end }} {{- end }}

{{ prefixes 6 "\t" $opts.Plain }}


{{- if $opts.Notes }}
{{- if $opts.Plain }}

NOTES:
{{- else }}

{{ bold "Notes:" }}
{{- end }}
{{ notes 6 "\t" $opts.Plain }}
{{- end }}
{{- end }} {{- end }}

View File

@ -1,19 +1,21 @@
package main package main


type Args struct { type Args struct {
Version bool `short:"v" long:"version" description:"Print the version and exit."` 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."` 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"` 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"` 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"` 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"` 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"` ExplicitNetwork XNetArgs `command:"net" alias:"n" description:"Print information about an explicit network address." validate:"omitempty"`
Table TableArgs `command:"table" alias:"t" alias:"tab" alias:"tbl" description:"Show prefix summaries (by default both IPv4 and IPv6)." 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 { type outputOpts struct {
SuppressRemaining bool `short:"r" long:"no-remaining" description:"Don't show leftover/unallocated/remaining space.'"` 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)."` 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."` 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."` 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 { type TableArgs struct {
tableOpts 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 { type VLSMArgs struct {
@ -62,5 +70,5 @@ type VLSMArgs struct {
} }


type Net struct { type Net struct {
Network string `positional-arg-name:"<network>/<prefix>" description:"network address with prefix. Can be IPv4 or IPv6." validate:"required,cidr"` Network string `positional-arg-name:"<network>/<prefix>" description:"Network address with prefix. Can be IPv4 or IPv6." validate:"required,cidr"`
} }

View File

@ -31,9 +31,11 @@ var (
tplDir embed.FS tplDir embed.FS
tblTpl *template.Template = template.Must(template.New("").Funcs( tblTpl *template.Template = template.Must(template.New("").Funcs(
template.FuncMap{ template.FuncMap{
"legacy4": tplClass4Iter, "bold": tplFmtBold,
"addrs": tplAddrIter, "legacy4": tplTableLegacy4,
"mask4": tplMaskIter4, "mask4": tplTableMasks4,
"notes": tplTableNotes,
"prefixes": tplTablePrefixes,
}, },
).ParseFS(tplDir, "_tpl/*.tpl")) ).ParseFS(tplDir, "_tpl/*.tpl"))
) )
@ -41,9 +43,19 @@ var (
var ( var (
// Primarily output formatting stuff in this block. // Primarily output formatting stuff in this block.
sectSepCnt = 48 sectSepCnt = 48
sectSep1 = strings.Repeat("=", sectSepCnt) // sectFmts contains a lookup of map[<is plain>][]string{<level 1>, <level 2>, <level 3>}
sectSep2 = strings.Repeat("-", sectSepCnt) sectFmts map[bool][]string = map[bool][]string{
sectSep3 = strings.Repeat(".", sectSepCnt) 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[<is plain>]*tableFormatter. // tblFmts contains a lookup of map[<is plain>]*tableFormatter.
tblFmts map[bool]*tableFormatter = map[bool]*tableFormatter{ tblFmts map[bool]*tableFormatter = map[bool]*tableFormatter{
// Plaintext/ASCII-only // 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",
},
}
)

View File

@ -219,6 +219,10 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
var invertedMask net.IPMask var invertedMask net.IPMask
var res *netsplit.StructuredResults var res *netsplit.StructuredResults
var verb = -1 var verb = -1
var fmts []string
var sectSep1 string
var sectSep2 string
// var sectSep3 string


if orig == nil { if orig == nil {
return 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 { if args.outputOpts.Verbose != nil {
verb = 0 verb = 0
@ -311,7 +319,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
} }
} else { } else {
for _, n := range nets { 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 { if verb >= 1 {
@ -335,7 +343,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
return return
} }
for _, n := range remaining.Prefixes() { 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 { if verb >= 1 {

View File

@ -6,13 +6,17 @@ import (
"strings" "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 pfx netip.Prefix
var bullet string = "+" var bullet string = "+"
var sb *strings.Builder = new(strings.Builder) var sb *strings.Builder = new(strings.Builder)
var pre string = strings.Repeat(indentStr, indent) var pre string = strings.Repeat(indentStr, indent)
var pre2 string = strings.Repeat(indentStr, indent+1) 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 { if s == nil {
return return

View File

@ -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
}

View File

@ -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
}

View File

@ -15,20 +15,187 @@ import (
`subnetter/netsplit` `subnetter/netsplit`
) )


/* // renderHdr renders a header. Note that the first line does *not* include the indent, but subsequent ones do.
tplClass4Iter should only be called if legacy info is enabled. func renderHdr(titles []string, colSizes []uint8, indent string, plain bool, sb *strings.Builder) {
It returns a tableLegacy4Sizer and a slice of tableLegacy4.


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. // This whole thing feels dirty.
// It's like adding a microcontroller to a rock. // It's like adding a microcontroller to a rock.
// But it works. // 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 pfx *net.IPNet
var row tableLegacy4
var rows []tableLegacy4
var rowVal reflect.Value
var classNets []*netip.Prefix var classNets []*netip.Prefix
var netRange netipx.IPRange var netRange netipx.IPRange
var sb *strings.Builder = new(strings.Builder)
var v *netsplit.VLSMSplitter = &netsplit.VLSMSplitter{ var v *netsplit.VLSMSplitter = &netsplit.VLSMSplitter{
Ascending: false, Ascending: false,
PrefixLengths: []uint8{ PrefixLengths: []uint8{
@ -48,53 +215,124 @@ func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) {
if classNets, _, err = v.Split(); err != nil { if classNets, _, err = v.Split(); err != nil {
return return
} }
rows = make([]tableLegacy4, 5)


legacySpec = &tableLegacy4Ret{ for idx, cls = range []string{
Sizer: &tableLegacy4Sizer{
Class: 5, // "CLASS"
CIDR: 4, // "BITS"
Start: 5, // "START"
End: 3, // "END"
},
Rows: make([]tableLegacy4, 5),
}
for idx, cls := range []string{
"A", "B", "C", "D", "E", "A", "B", "C", "D", "E",
} { } {
legacySpec.Rows[idx] = tableLegacy4{ rows[idx] = tableLegacy4{
Class: cls, Class: cls,
CIDR: classNets[idx].String(), CIDR: classNets[idx].String(),
NetCIDR: *classNets[idx], NetCIDR: *classNets[idx],
} }
netRange = netipx.RangeOfPrefix(legacySpec.Rows[idx].NetCIDR) netRange = netipx.RangeOfPrefix(rows[idx].NetCIDR)
legacySpec.Rows[idx].NetStart = netRange.From() rows[idx].NetStart = netRange.From()
legacySpec.Rows[idx].NetEnd = netRange.To() rows[idx].NetEnd = netRange.To()
legacySpec.Rows[idx].Start = legacySpec.Rows[idx].NetStart.String() rows[idx].Start = rows[idx].NetStart.String()
legacySpec.Rows[idx].End = legacySpec.Rows[idx].NetEnd.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 return
} }


/* /*
tplAddrIter takes a 4 or 6 for inet family/version and returns a tableAddrSizer and tplTableMasks4 renders a table of netmasks for IPv4.
slice of tableAddr. IPv6 doesn't really use netmasks, instead relying almost entirely upon CIDR.
tableAddr is sorted from smallest prefix/largest network to largest prefix/smallest network.
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 dummyNet *net.IPNet
var l int var dummyAddr netip.Addr
var rowVal reflect.Value
var sb *strings.Builder = new(strings.Builder)


addrs = &tableAddrRet{ if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
Sizer: &tableAddrSizer{ return
Prefix: 6, // "PREFIX"
Bits: 4, // "BITS"
Addresses: 9, // "ADDRESSES"
Hosts: 5, // "HOSTS"
},
} }
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 { switch ipVer {
case 4: case 4:
@ -109,339 +347,185 @@ func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) {
err = errBadNet err = errBadNet
return return
} }
rows = make([]tablePrefixNote, 0, dummyAddr.BitLen()+1)


// Before we size, we generate the tableAddrs. for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
addrs.Rows = make([]tableAddr, dummyAddr.BitLen()+1) if note, ok = netNotes[ipVer][uint8(idx)]; !ok {
for i := 0; i <= dummyAddr.BitLen(); i++ { continue
addrs.Rows[i] = tableAddr{
Prefix: uint8(i),
Bits: uint8(dummyAddr.BitLen() - i),
} }
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 return
} }
dummyNet = netipx.PrefixIPNet(addrs.Rows[i].NetPrefix.Masked()) rows = append(rows, row)
addrs.Rows[i].Addresses = mapcidr.CountIPsInCIDR(true, true, dummyNet)
addrs.Rows[i].Hosts = mapcidr.CountIPsInCIDR(false, false, dummyNet)
} }


// Now the sizer. The padding itself is handled in different logic, just need the length of the longest value as a string. colFields, colTitles, colSizes = sizeStructs(rows)
for _, addr := range addrs.Rows {
// I *abhor* walrus operators in anything but loops. // Header
l = len(strconv.Itoa(int(addr.Prefix))) renderHdr(colTitles, colSizes, indent, plain, sb)
if int(addrs.Sizer.Prefix) < l {
addrs.Sizer.Prefix = uint8(l) // Rows
} for idx, row = range rows {
l = len(strconv.Itoa(int(addr.Bits))) rowVal = reflect.ValueOf(row)
if int(addrs.Sizer.Bits) < l { isLast = idx == len(rows)-1
addrs.Sizer.Bits = uint8(l) renderRow(rowVal, colFields, colSizes, indent, plain, isLast, sb)
}
// 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)
}
} }


out = sb.String()

return return
} }


/* /*
tplMaskIter4 returns a slice of IPv4 netmasks and returns a slice of tableMask4. tplTablePrefixes renders a table of prefixes for IP version/inet family ipVer (4 or 6).
Sorted from smallest prefix/largest network to largest prefix/smallest network.
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 idx int
var pfx netip.Prefix var isLast bool
var colFields []int
var colSizes []uint8
var colTitles []string
var row tablePrefix
var rows []tablePrefix
var dummyNet *net.IPNet var dummyNet *net.IPNet
var l int var dummyAddr netip.Addr
var rowVal reflect.Value
var sb *strings.Builder = new(strings.Builder)


masks = &tableMask4Ret{ switch ipVer {
Sizer: &tableMask4Sizer{ case 4:
Prefix: 6, // "PREFIX" if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
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 {
return return
} }
dummyNet = netipx.PrefixIPNet(pfx.Masked()) case 6:
masks.Rows[i] = tableMask4{ if dummyAddr, err = netip.ParseAddr("::"); err != nil {
Prefix: uint8(i), return
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,
} }
} default:

err = errBadNet
// 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 {
return return
} }
rows = make([]tablePrefix, dummyAddr.BitLen()+1)


val = hdrVal for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
valType = val.Type() rows[idx] = tablePrefix{
lastField = valType.NumField() - 1 Prefix: fmt.Sprintf("/%d", idx),

Bits: uint8(dummyAddr.BitLen() - idx),
for i := val.NumField(); i >= 0; i-- {
field = valType.Field(i - 1)
if field.Tag.Get("render") == "-" {
continue
} }
lastField = i if rows[idx].NetPrefix, err = dummyAddr.Prefix(idx); err != nil {
break 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) colFields, colTitles, colSizes = sizeStructs(rows)
if isLastLine {
sb.WriteString(tfmt.LastLeft) // Header
} else { renderHdr(colTitles, colSizes, indent, plain, sb)
sb.WriteString(tfmt.LineLeft)
// 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() out = sb.String()


return return
} }


// do not include in template funcs; used externally // rows should be a slice of structs/struct ptrs of all the same type (you're going to get errors/panics otherwise).
func rowRender(val reflect.Value, sizerVal reflect.Value, indent string, plain bool) (out string) { 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 field reflect.StructField
var fieldVal reflect.Value 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) // Here be dragons. This is *NOT SAFE* for generic (heh) consumption.
for i := 0; i < val.NumField(); i++ { 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) field = valType.Field(i)
if field.Tag.Get("render") == "-" { if field.Tag.Get("render") == "-" {
continue continue
} }
sb.WriteString(tfmt.Left) title = field.Tag.Get("renderTitle")
fieldVal = val.Field(i) if title == "" {
sizerName = field.Tag.Get("renderSizeName") title = field.Name
if sizerName == "" {
sizerName = field.Name
} }
sizerField = sizerVal.FieldByName(sizerName) size = len(title) + (fixedPad * len(padChars))
colLen = uint8(sizerField.Uint()) + uint8(fixedPad) colTitles = append(colTitles, title)
switch fieldVal.Kind() { colSizes = append(colSizes, uint8(size))
// This is tailored specifically to this implementation. colFieldIdx = append(colFieldIdx, i)
case reflect.String: }
sb.WriteString(fieldVal.String()) // colTitles is done.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Int()), colLen)) // check each field in each row to see if its value's size is greater than the current.
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: for _, row := range rowVals {
sb.WriteString(padStr(fmt.Sprintf("%d", fieldVal.Uint()), colLen)) val = reflect.Indirect(row)
case reflect.Ptr: if val.Kind() != reflect.Struct {
// It's a *big.Int. continue
if fieldVal.IsNil() { }
sb.WriteString(padStr(strings.Repeat(padChars, int(colLen)), colLen)) for colIdx, fieldIdx = range colFieldIdx {
} else { fieldVal = val.Field(fieldIdx)
// TIL you can even *do* this in reflection. // 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] fieldVal = fieldVal.MethodByName("String").Call(nil)[0]
callVal = fieldVal.String() valStr = fieldVal.String()
sb.WriteString(padStr(callVal, colLen)) }
valLen = len(valStr) + (fixedPad * len(padChars))
if valLen > int(colSizes[colIdx]) {
colSizes[colIdx] = uint8(valLen)
} }
} }
} }
sb.WriteString("\n")

out = sb.String()


return return
} }

View File

@ -67,7 +67,36 @@ func main() {
} }


switch parser.Active.Name { 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": 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) 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.tableOpts); err != nil {
log.Panicln(err) log.Panicln(err)

View File

@ -10,6 +10,7 @@ import (
type subnetResult netip.Prefix type subnetResult netip.Prefix


type tableOpts struct { 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."` Plain bool `short:"p" long:"plain" description:"Show plain table output."`
Legacy bool `short:"l" long:"legacy" description:"Include legacy/obsolete/deprecated information."` 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."` 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)."` 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. // tableLegacy4 contains a spec for a class in the legacy "classed" IPv4 networking.
type tableLegacy4 struct { type tableLegacy4 struct {
Class string Class string
@ -78,12 +29,31 @@ type tableLegacy4 struct {
NetCIDR netip.Prefix `render:"-"` NetCIDR netip.Prefix `render:"-"`
} }


// tableLegacy4Sizer is used to size tableLegacy4 entries. // tableMask4 is used to hold string representation of netmask information.
type tableLegacy4Sizer struct { type tableMask4 struct {
Class uint8 Prefix string
CIDR uint8 Netmask string
Start uint8 Hex string
End uint8 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. // tableFormatter is used for "rendering" table output.

1
go.mod
View File

@ -6,6 +6,7 @@ toolchain go1.23.5


require ( require (
github.com/TwiN/go-color v1.4.1 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/go-playground/validator/v10 v10.24.0
github.com/goccy/go-yaml v1.15.16 github.com/goccy/go-yaml v1.15.16
github.com/jessevdk/go-flags v1.6.1 github.com/jessevdk/go-flags v1.6.1

12
netsplit/conts.go Normal file
View File

@ -0,0 +1,12 @@
package netsplit

import (
`net/netip`
)

var (
ReservedNets map[netip.Prefix]string
reservedNetsOrig map[string]string = map[string]string{
"": "",
}
)

View File

@ -1,3 +1,20 @@
package netsplit 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
}
}