448 lines
11 KiB
Go
448 lines
11 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`
|
|
`subnetter/netsplit`
|
|
)
|
|
|
|
/*
|
|
tplClass4Iter should only be called if legacy info is enabled.
|
|
It returns a tableLegacy4Sizer and a slice of tableLegacy4.
|
|
|
|
It takes no input.
|
|
*/
|
|
func tplClass4Iter() (legacySpec *tableLegacy4Ret, err error) {
|
|
|
|
// This whole thing feels dirty.
|
|
// It's like adding a microcontroller to a rock.
|
|
// But it works.
|
|
var pfx *net.IPNet
|
|
var classNets []*netip.Prefix
|
|
var netRange netipx.IPRange
|
|
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
|
|
}
|
|
|
|
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{
|
|
"A", "B", "C", "D", "E",
|
|
} {
|
|
legacySpec.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()
|
|
}
|
|
|
|
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.
|
|
*/
|
|
func tplAddrIter(ipVer uint8) (addrs *tableAddrRet, err error) {
|
|
|
|
var dummyAddr netip.Addr
|
|
var dummyNet *net.IPNet
|
|
var l int
|
|
|
|
addrs = &tableAddrRet{
|
|
Sizer: &tableAddrSizer{
|
|
Prefix: 6, // "PREFIX"
|
|
Bits: 4, // "BITS"
|
|
Addresses: 9, // "ADDRESSES"
|
|
Hosts: 5, // "HOSTS"
|
|
},
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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),
|
|
}
|
|
if addrs.Rows[i].NetPrefix, err = dummyAddr.Prefix(i); 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
func tplMaskIter4() (masks *tableMask4Ret, err error) {
|
|
|
|
var dummyAddr netip.Addr
|
|
var pfx netip.Prefix
|
|
var dummyNet *net.IPNet
|
|
var l int
|
|
|
|
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 {
|
|
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,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
}
|
|
lastField = i
|
|
break
|
|
}
|
|
|
|
sb.WriteString(indent)
|
|
if isLastLine {
|
|
sb.WriteString(tfmt.LastLeft)
|
|
} else {
|
|
sb.WriteString(tfmt.LineLeft)
|
|
}
|
|
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) {
|
|
|
|
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++ {
|
|
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
|
|
}
|
|
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.
|
|
fieldVal = fieldVal.MethodByName("String").Call(nil)[0]
|
|
callVal = fieldVal.String()
|
|
sb.WriteString(padStr(callVal, colLen))
|
|
}
|
|
}
|
|
}
|
|
sb.WriteString("\n")
|
|
|
|
out = sb.String()
|
|
|
|
return
|
|
}
|