go_subnetter/cmd/subnetter/funcs_tpl.go
brent saner 166fb3be23
v0.0.2
FIXED:
* Ooops, fix go mod
2025-03-10 10:07:51 -04:00

532 lines
13 KiB
Go

package main
import (
`encoding/binary`
`fmt`
`net`
`net/netip`
`reflect`
`strconv`
`strings`
`github.com/TwiN/go-color`
`github.com/projectdiscovery/mapcidr`
`go4.org/netipx`
`r00t2.io/subnetter/netsplit`
)
// 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) {
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 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{
1, // A
2, // B
3, // C
4, // D
4, // E
},
BaseSplitter: new(netsplit.BaseSplitter),
}
if _, pfx, err = net.ParseCIDR("0.0.0.0/0"); err != nil {
return
}
v.SetParent(*pfx)
if classNets, _, err = v.Split(); err != nil {
return
}
rows = make([]tableLegacy4, 5)
for idx, cls = range []string{
"A", "B", "C", "D", "E",
} {
rows[idx] = tableLegacy4{
Class: cls,
CIDR: classNets[idx].String(),
NetCIDR: *classNets[idx],
}
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
}
/*
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 tplTableMasks4(indent string, plain bool) (out string, err error) {
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 dummyAddr netip.Addr
var rowVal reflect.Value
var sb *strings.Builder = new(strings.Builder)
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:
if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
return
}
case 6:
if dummyAddr, err = netip.ParseAddr("::"); err != nil {
return
}
default:
err = errBadNet
return
}
rows = make([]tablePrefixNote, 0, dummyAddr.BitLen()+1)
for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
if note, ok = netNotes[ipVer][uint8(idx)]; !ok {
continue
}
row = tablePrefixNote{
Prefix: fmt.Sprintf("/%d", idx),
Note: note,
}
if row.NetPrefix, err = dummyAddr.Prefix(idx); err != nil {
return
}
rows = append(rows, row)
}
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
}
/*
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 tplTablePrefixes(ipVer uint8, indent string, plain bool) (out string, err error) {
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 dummyAddr netip.Addr
var rowVal reflect.Value
var sb *strings.Builder = new(strings.Builder)
switch ipVer {
case 4:
if dummyAddr, err = netip.ParseAddr("0.0.0.0"); err != nil {
return
}
case 6:
if dummyAddr, err = netip.ParseAddr("::"); err != nil {
return
}
default:
err = errBadNet
return
}
rows = make([]tablePrefix, dummyAddr.BitLen()+1)
for idx = 0; idx <= dummyAddr.BitLen(); idx++ {
rows[idx] = tablePrefix{
Prefix: fmt.Sprintf("/%d", idx),
Bits: uint8(dummyAddr.BitLen() - idx),
}
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)
}
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
}
// 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
// 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
}
title = field.Tag.Get("renderTitle")
if title == "" {
title = field.Name
}
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]
valStr = fieldVal.String()
}
valLen = len(valStr) + (fixedPad * len(padChars))
if valLen > int(colSizes[colIdx]) {
colSizes[colIdx] = uint8(valLen)
}
}
}
return
}