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
: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^]).

View File

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

View File

@ -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:"<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
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[<is plain>][]string{<level 1>, <level 2>, <level 3>}
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[<is plain>]*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",
},
}
)

View File

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

View File

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

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`
)

/*
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
}

View File

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

View File

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

1
go.mod
View File

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

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

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