this is cool and all but the tables don't render properly
This commit is contained in:
parent
b09cb83017
commit
3a7ed5973b
43
.gitignore
vendored
43
.gitignore
vendored
@ -1 +1,42 @@
|
||||
cmd/subnetter/subnetter
|
||||
*.7z
|
||||
*.bak
|
||||
*.deb
|
||||
*.jar
|
||||
*.log
|
||||
*.rar
|
||||
*.run
|
||||
*.sig
|
||||
*.tar
|
||||
*.tar.bz2
|
||||
*.tar.gz
|
||||
*.tar.xz
|
||||
*.tbz
|
||||
*.tbz2
|
||||
*.tgz
|
||||
*.txz
|
||||
*.zip
|
||||
.*.swp
|
||||
.editix
|
||||
.idea/
|
||||
|
||||
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
/bin/*
|
||||
/_examples
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
# But allow test suite.
|
||||
!*test.go
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
127
build.sh
Executable file
127
build.sh
Executable file
@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# This is not portable. It has bashisms.
|
||||
|
||||
BUILD_TIME="$(date '+%s')"
|
||||
BUILD_USER="$(whoami)"
|
||||
BUILD_SUDO_USER="${SUDO_USER}"
|
||||
BUILD_HOST="$(hostname)"
|
||||
|
||||
# Check to make sure git is available.
|
||||
if ! command -v git &> /dev/null;
|
||||
then
|
||||
echo "Git is not available; automatic version handling unsupported."
|
||||
echo "You must build by calling 'go build' directly in the respective directories."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check git directory/repository.
|
||||
if ! git rev-parse --is-inside-work-tree &>/dev/null;
|
||||
then
|
||||
echo "Not running inside a git work tree; automatic version handling unsupported/build script unsupported."
|
||||
echo "You must build by calling 'go build' directly in the respective directories instead."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The repo URI for origin.
|
||||
REPO_URI="$(git remote get-url origin)"
|
||||
|
||||
# If it has a tag in the path of the current HEAD that matches a version string...
|
||||
# I wish git describe supported regex. It does not; only globs. Gross.
|
||||
# If there's a bug anywhere, it's here.
|
||||
if git describe --tags --abbrev=0 --match "v[0-9]*" HEAD &> /dev/null;
|
||||
then
|
||||
# It has a tag we can use.
|
||||
CURRENT_VER="$(git describe --tags --abbrev=0 --match "v[0-9]*" HEAD)"
|
||||
COMMITS_SINCE="$(git rev-list --count ${CURRENT_VER}..HEAD)"
|
||||
else
|
||||
# No tag available.
|
||||
CURRENT_VER=""
|
||||
COMMITS_SINCE=""
|
||||
fi
|
||||
|
||||
# If it's dirty (staged but not committed or unstaged files)...
|
||||
if ! git diff-index --quiet HEAD;
|
||||
then
|
||||
# It's dirty.
|
||||
IS_DIRTY="1"
|
||||
else
|
||||
# It's clean.
|
||||
IS_DIRTY="0"
|
||||
fi
|
||||
|
||||
# Get the commit hash of the *most recent* commit in the path of current HEAD...
|
||||
CURRENT_HASH="$(git rev-parse --verify HEAD)"
|
||||
# The same as above, but abbreviated.
|
||||
CURRENT_SHORT="$(git rev-parse --verify --short HEAD)"
|
||||
|
||||
# Get the module name.
|
||||
MODPATH="$(sed -n -re 's@^\s*module\s+(.*)(//.*)?$@\1@p' go.mod)"
|
||||
|
||||
# Build the ldflags string.
|
||||
LDFLAGS_STR="\
|
||||
-X '${MODPATH}/version.repoUri=${REPO_URI}' \
|
||||
-X '${MODPATH}/version.sourceControl=git' \
|
||||
-X '${MODPATH}/version.version=${CURRENT_VER}' \
|
||||
-X '${MODPATH}/version.commitHash=${CURRENT_HASH}' \
|
||||
-X '${MODPATH}/version.commitShort=${CURRENT_SHORT}' \
|
||||
-X '${MODPATH}/version.numCommitsAfterTag=${COMMITS_SINCE}' \
|
||||
-X '${MODPATH}/version.isDirty=${IS_DIRTY}' \
|
||||
-X '${MODPATH}/version.buildTime=${BUILD_TIME}' \
|
||||
-X '${MODPATH}/version.buildUser=${BUILD_USER}' \
|
||||
-X '${MODPATH}/version.buildSudoUser=${BUILD_SUDO_USER}' \
|
||||
-X '${MODPATH}/version.buildHost=${BUILD_HOST}'"
|
||||
|
||||
|
||||
# And finally build.
|
||||
export CGO_ENABLED=0
|
||||
|
||||
mkdir -p ./bin/
|
||||
|
||||
cmd="subnetter"
|
||||
# Linux
|
||||
bin="${cmd}"
|
||||
echo "Building ./bin/${bin}..."
|
||||
go build \
|
||||
-o "./bin/${bin}" \
|
||||
-ldflags \
|
||||
"${LDFLAGS_STR}" \
|
||||
cmd/${cmd}/*.go
|
||||
echo " Done."
|
||||
|
||||
# Windows
|
||||
GOOS="windows"
|
||||
bin="subnetter.exe"
|
||||
echo "Building ./bin/${bin}..."
|
||||
go build \
|
||||
-o "./bin/${bin}" \
|
||||
-ldflags \
|
||||
"${LDFLAGS_STR}" \
|
||||
cmd/${cmd}/*.go
|
||||
echo " Done."
|
||||
|
||||
# macOS
|
||||
GOOS="darwin"
|
||||
## x86_64
|
||||
bin="subnetter.x86_64.app"
|
||||
echo "Building ./bin/${bin}..."
|
||||
go build \
|
||||
-o "./bin/${bin}" \
|
||||
-ldflags \
|
||||
"${LDFLAGS_STR}" \
|
||||
cmd/${cmd}/*.go
|
||||
echo " Done."
|
||||
## ARM64 (m1/m2/... etc.; "Apple Silicon")
|
||||
GOARCH="arm64"
|
||||
bin="subnetter.m1.app"
|
||||
echo "Building ./bin/${bin}..."
|
||||
go build \
|
||||
-o "./bin/${bin}" \
|
||||
-ldflags \
|
||||
"${LDFLAGS_STR}" \
|
||||
cmd/${cmd}/*.go
|
||||
echo " Done."
|
||||
|
||||
echo "Build complete."
|
52
cmd/subnetter/_tpl/table.tpl
Normal file
52
cmd/subnetter/_tpl/table.tpl
Normal file
@ -0,0 +1,52 @@
|
||||
{{- /*gotype: subnetter/cmd/subnetter.tableOpts*/ -}}
|
||||
{{- $opts := . -}}
|
||||
{{- $numRows := 0 -}}
|
||||
{{- if not $opts.NoIpv4 }}
|
||||
IPv4:
|
||||
{{- if $opts.Legacy -}}
|
||||
{{- $legacyspec := legacy4 }}
|
||||
{{- $numRows = len $legacyspec.Rows }}
|
||||
|
||||
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 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if not $opts.NoV4Mask }}
|
||||
{{- $masks := mask4 }}
|
||||
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 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
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 }}
|
||||
|
||||
{{- 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 }}
|
||||
|
||||
{{- end }}
|
@ -1,19 +1,21 @@
|
||||
package main
|
||||
|
||||
type Args struct {
|
||||
SplitCIDR SplitCIDRArgs `command:"split-cidr" alias:"se" description:"Split a network into as many equal parts of a given prefix as possible." validate:"omitempty"`
|
||||
SplitHost SplitHostArgs `command:"split-hosts" alias:"sh" description:"Split a network into n total number of hosts into subnet as cleanly/evenly 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"`
|
||||
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"`
|
||||
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. 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)."`
|
||||
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. DO NOT parse 'pretty' as its output is not guaranteed between versions."`
|
||||
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."`
|
||||
}
|
||||
|
||||
type common struct {
|
||||
@ -36,21 +38,21 @@ type SplitCIDRArgs struct {
|
||||
}
|
||||
|
||||
type SplitHostArgs struct {
|
||||
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
|
||||
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of hosts/assignable addresses in a subnet is not exactly -n/--num-hosts."`
|
||||
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
|
||||
common
|
||||
}
|
||||
|
||||
type SplitSubnetArgs struct {
|
||||
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of possible equally-sized subnets is not exactly -n/--num-nets."`
|
||||
NumNets uint `short:"n" long:"num-nets" required:"true" description:"Number of networks." validate:"required"`
|
||||
common
|
||||
}
|
||||
|
||||
type TableArgs struct {
|
||||
NoIpv6 bool `short:"4" long:"ipv4" description:"Show IPv4 table."`
|
||||
NoIpv4 bool `short:"6" long:"ipv6" description:"Show IPv6 table."`
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose information. May be specified multiple times to increase verbosity (up to 3 levels)."`
|
||||
Fmt string `short:"f" long:"format" choice:"csv" choice:"json" choice:"pretty" choice:"tsv" choice:"yml" choice:"xml" default:"pretty" description:"Output format."`
|
||||
Net *string `short:"n" long:"network" description:"If specified, provide information explicitly about this network. Ignores -4/--ipv4 and -6/--ipv6." validate:"omitempty,cidr"`
|
||||
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 VLSMArgs struct {
|
||||
|
@ -1,18 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
`embed`
|
||||
"strings"
|
||||
`text/template`
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
var (
|
||||
args *Args = new(Args)
|
||||
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
args = new(Args)
|
||||
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
)
|
||||
|
||||
const (
|
||||
/*
|
||||
fixedPad is a fixed "surrounding" pad always present (as minimum), even for values max len on columns.
|
||||
*Must* be positive even, as <fixed left pad> and <fixed right pad> == fixedPad/2.
|
||||
*/
|
||||
fixedPad int = 2
|
||||
/*
|
||||
padChars is what fills the pads in table cells.
|
||||
At the *LEAST*, a cell will be "<fixedPad/2 * padChars><str><fixedPad/2 * padChars>"
|
||||
*/
|
||||
padChars string = " "
|
||||
)
|
||||
|
||||
var (
|
||||
sectSepCnt int = 48
|
||||
sectSep1 string = strings.Repeat("=", sectSepCnt)
|
||||
sectSep2 string = strings.Repeat("-", sectSepCnt)
|
||||
sectSep3 string = strings.Repeat(".", sectSepCnt)
|
||||
//go:embed "_tpl"
|
||||
tplDir embed.FS
|
||||
tblTpl *template.Template = template.Must(template.New("").Funcs(
|
||||
template.FuncMap{
|
||||
"legacy4": tplClass4Iter,
|
||||
"addrs": tplAddrIter,
|
||||
"mask4": tplMaskIter4,
|
||||
},
|
||||
).ParseFS(tplDir, "_tpl/*.tpl"))
|
||||
)
|
||||
|
||||
var (
|
||||
// Primarily output formatting stuff in this block.
|
||||
sectSepCnt = 48
|
||||
sectSep1 = strings.Repeat("=", sectSepCnt)
|
||||
sectSep2 = strings.Repeat("-", sectSepCnt)
|
||||
sectSep3 = strings.Repeat(".", sectSepCnt)
|
||||
// tblFmts contains a lookup of map[<is plain>]*tableFormatter.
|
||||
tblFmts map[bool]*tableFormatter = map[bool]*tableFormatter{
|
||||
// Plaintext/ASCII-only
|
||||
true: &tableFormatter{
|
||||
TopLeftHdr: "*", // Or _
|
||||
TopFillHdr: "*", // ""
|
||||
TopColSepHdr: "*", // ""
|
||||
TopRightHdr: "*", // ""
|
||||
ColSepHdr: "|",
|
||||
BottomLeftHdr: "*", // Or +
|
||||
BottomFillHdr: "*", // Or -
|
||||
BottomColSepHdr: "*", // Or +
|
||||
BottomRightHdr: "*", // ""
|
||||
Left: "|",
|
||||
Fill: "-",
|
||||
LineColSep: "|",
|
||||
LineLeft: "|",
|
||||
LineRight: "|",
|
||||
ColSep: "|",
|
||||
Right: "|",
|
||||
LastLeft: "+",
|
||||
LastFill: "-",
|
||||
LastSep: "-",
|
||||
LastRight: "+",
|
||||
SuppressLineSep: true,
|
||||
NoUpperTitle: false,
|
||||
NoBoldTitle: true,
|
||||
},
|
||||
// Unicode/UTF-8
|
||||
// https://en.wikipedia.org/wiki/Box-drawing_characters
|
||||
false: &tableFormatter{
|
||||
TopLeftHdr: "┏",
|
||||
TopFillHdr: "━",
|
||||
TopColSepHdr: "┳",
|
||||
TopRightHdr: "┓",
|
||||
ColSepHdr: "┃",
|
||||
BottomLeftHdr: "┣",
|
||||
BottomFillHdr: "━",
|
||||
BottomColSepHdr: "╇",
|
||||
BottomRightHdr: "┫",
|
||||
Left: "┃",
|
||||
Fill: "─",
|
||||
LineColSep: "┼",
|
||||
LineLeft: "┠",
|
||||
LineRight: "┨",
|
||||
ColSep: "│",
|
||||
Right: "┃",
|
||||
LastLeft: "┗",
|
||||
LastFill: "━",
|
||||
LastSep: "┷",
|
||||
LastRight: "┛",
|
||||
SuppressLineSep: false,
|
||||
NoUpperTitle: true,
|
||||
NoBoldTitle: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
9
cmd/subnetter/errs.go
Normal file
9
cmd/subnetter/errs.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
`errors`
|
||||
)
|
||||
|
||||
var (
|
||||
errBadNet error = errors.New("bad inet/addr family/version")
|
||||
)
|
@ -6,24 +6,26 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/projectdiscovery/mapcidr"
|
||||
"go4.org/netipx"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"subnetter/netsplit"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/projectdiscovery/mapcidr"
|
||||
"go4.org/netipx"
|
||||
"subnetter/netsplit"
|
||||
`subnetter/version`
|
||||
)
|
||||
|
||||
func printHostPrefix(label string, pfx *netip.Prefix, verb, indent int, indentStr string) (out string) {
|
||||
|
||||
var maskEvery uint
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var pre string = strings.Repeat(indentStr, indent)
|
||||
var pre2 string = strings.Repeat(indentStr, indent+1)
|
||||
var sb = new(strings.Builder)
|
||||
var pre = strings.Repeat(indentStr, indent)
|
||||
var pre2 = strings.Repeat(indentStr, indent+1)
|
||||
|
||||
if pfx == nil {
|
||||
fmt.Fprintf(sb, "%s%s:\n%sAddress:\t(N/A)\n", pre, label, pre2)
|
||||
@ -102,10 +104,10 @@ func printMask(label string, pfx netip.Prefix, verb, indent int, indentStr strin
|
||||
var mask net.IPMask
|
||||
var first netip.Addr
|
||||
var last netip.Addr
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var pre string = strings.Repeat(indentStr, indent)
|
||||
var pre2 string = strings.Repeat(indentStr, indent+1)
|
||||
var pre3 string = strings.Repeat(indentStr, indent+2)
|
||||
var sb = new(strings.Builder)
|
||||
var pre = strings.Repeat(indentStr, indent)
|
||||
var pre2 = strings.Repeat(indentStr, indent+1)
|
||||
var pre3 = strings.Repeat(indentStr, indent+2)
|
||||
|
||||
if !pfx.IsValid() {
|
||||
return
|
||||
@ -156,7 +158,8 @@ func printMask(label string, pfx netip.Prefix, verb, indent int, indentStr strin
|
||||
fmt.Fprintf(sb, "%sBits:\t\t%d\n", pre2, pfx.Bits())
|
||||
fmt.Fprintf(sb, "%sFirst:\t\t%s\n", pre2, first.String())
|
||||
fmt.Fprintf(sb, "%sLast:\t\t%s\n", pre2, last.String())
|
||||
fmt.Fprintf(sb, "%sAddresses:\t%d\n", pre2, mapcidr.CountIPsInCIDR())
|
||||
fmt.Fprintf(sb, "%sAddresses:\t%d\n", pre2, mapcidr.CountIPsInCIDR(true, true, netipx.PrefixIPNet(pfx.Masked())))
|
||||
fmt.Fprintf(sb, "%sHosts:\t\t%d\n", pre2, mapcidr.CountIPsInCIDR(false, false, netipx.PrefixIPNet(pfx.Masked())))
|
||||
if verb >= 2 {
|
||||
fmt.Fprintf(sb, "%sExpanded:\t%s\n", pre2, netsplit.MaskExpand(mask, pfx.Addr().Is6()))
|
||||
fmt.Fprintf(sb, "%sHex:\t\t0x%s\n", pre2, mask.String())
|
||||
@ -215,7 +218,7 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
var remPfxs []*netip.Prefix
|
||||
var invertedMask net.IPMask
|
||||
var res *netsplit.StructuredResults
|
||||
var verb int = -1
|
||||
var verb = -1
|
||||
|
||||
if orig == nil {
|
||||
return
|
||||
@ -341,7 +344,6 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
}
|
||||
} else {
|
||||
buf = new(bytes.Buffer)
|
||||
// TODO: data-formatted/structured output
|
||||
if res, err = netsplit.Contain(orig, nets, remaining, splitter); err != nil {
|
||||
return
|
||||
}
|
||||
@ -355,11 +357,11 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
fmt.Fprintf(
|
||||
buf,
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`+
|
||||
"<!--\n"+
|
||||
" Generated by subnetter.\n"+
|
||||
"\n<!--\n"+
|
||||
" Generated by subnetter %s\n"+
|
||||
" %s\n"+
|
||||
"-->\n",
|
||||
time.Now().String(),
|
||||
version.Ver.Short(), time.Now().String(),
|
||||
)
|
||||
if b, err = xml.MarshalIndent(res, "", " "); err != nil {
|
||||
return
|
||||
@ -368,9 +370,9 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
case "yml":
|
||||
fmt.Fprintf(
|
||||
buf,
|
||||
"# Generated by subnetter.\n"+
|
||||
"# Generated by subnetter %s\n"+
|
||||
"# %s\n\n",
|
||||
time.Now().String(),
|
||||
version.Ver.Short(), time.Now().String(),
|
||||
)
|
||||
if b, err = yaml.Marshal(res); err != nil {
|
||||
return
|
||||
|
64
cmd/subnetter/funcs_tblfmt.go
Normal file
64
cmd/subnetter/funcs_tblfmt.go
Normal file
@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`strings`
|
||||
)
|
||||
|
||||
func padStr(str string, colSize uint8) (out string) {
|
||||
|
||||
var fill int
|
||||
var lFill int
|
||||
var rFill int
|
||||
var strLen int = len(str)
|
||||
|
||||
// EZPZ. Exact match.
|
||||
if strLen+fixedPad == int(colSize) {
|
||||
out = fmt.Sprintf(
|
||||
"%s%s%s",
|
||||
strings.Repeat(padChars, fixedPad/2), str, strings.Repeat(padChars, fixedPad/2),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// This is where it gets... annoying.
|
||||
fill = int(colSize) - (strLen + fixedPad)
|
||||
if fill%2 == 0 {
|
||||
/*
|
||||
Split evenly left/right.
|
||||
This condition will be met if BOTH strLen and colSize are even,
|
||||
or if BOTH strLen and colSize are odd.
|
||||
Math!
|
||||
*/
|
||||
lFill = fill / 2
|
||||
rFill = fill / 2
|
||||
} else {
|
||||
// Either the value or the width is odd, and the other is even.
|
||||
// (Note: Goland automatically floors an int/int calculation's result.)
|
||||
// As such, asymmetrical padding is needed.
|
||||
if strLen%2 == 0 {
|
||||
/*
|
||||
String is even, width is odd.
|
||||
Favor (smaller fill) the left.
|
||||
*/
|
||||
lFill = fill / 2
|
||||
rFill = (fill + 1) / 2 // This works instead of math.Ceil because dividing by 2.
|
||||
} else {
|
||||
/*
|
||||
String is odd, width is even.
|
||||
Favor right pad.
|
||||
*/
|
||||
lFill = (fill + 1) / 2
|
||||
rFill = fill / 2
|
||||
}
|
||||
}
|
||||
|
||||
out = fmt.Sprintf(
|
||||
"%s%s%s",
|
||||
strings.Repeat(padChars, lFill+fixedPad/2),
|
||||
str,
|
||||
strings.Repeat(padChars, rFill+fixedPad/2),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
54
cmd/subnetter/funcs_tblrows.go
Normal file
54
cmd/subnetter/funcs_tblrows.go
Normal file
@ -0,0 +1,54 @@
|
||||
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
|
||||
}
|
113
cmd/subnetter/funcs_tblsizers.go
Normal file
113
cmd/subnetter/funcs_tblsizers.go
Normal file
@ -0,0 +1,113 @@
|
||||
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
|
||||
}
|
447
cmd/subnetter/funcs_tpl.go
Normal file
447
cmd/subnetter/funcs_tpl.go
Normal file
@ -0,0 +1,447 @@
|
||||
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
|
||||
}
|
@ -3,14 +3,17 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go4.org/netipx"
|
||||
`fmt`
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"go4.org/netipx"
|
||||
"subnetter/netsplit"
|
||||
`subnetter/version`
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"r00t2.io/sysutils/paths"
|
||||
@ -29,8 +32,10 @@ func main() {
|
||||
var remaining *netipx.IPSet
|
||||
var buf *bytes.Buffer
|
||||
var res *netsplit.StructuredResults
|
||||
var splitErr *netsplit.SplitErr = new(netsplit.SplitErr)
|
||||
var parser *flags.Parser = flags.NewParser(args, flags.Default)
|
||||
var noStrict bool
|
||||
var strictErr error
|
||||
var splitErr = new(netsplit.SplitErr)
|
||||
var parser = flags.NewParser(args, flags.Default)
|
||||
|
||||
if _, err = parser.Parse(); err != nil {
|
||||
switch flagsErr := err.(type) {
|
||||
@ -46,12 +51,30 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if version.Ver, err = version.Version(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
// If args.Version or args.DetailVersion are true, just print them and exit.
|
||||
if args.DetailVersion || args.Version {
|
||||
if args.Version {
|
||||
fmt.Println(version.Ver.Short())
|
||||
return
|
||||
} else if args.DetailVersion {
|
||||
fmt.Println(version.Ver.Detail())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch parser.Active.Name {
|
||||
case "table":
|
||||
// TODO: print table and exit
|
||||
buf = new(bytes.Buffer)
|
||||
if err = tblTpl.ExecuteTemplate(buf, "table.tpl", args.Table.tableOpts); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
return
|
||||
case "parse":
|
||||
// TODO: parse file/bytes, unmarshal, and render with new options then exit
|
||||
if strings.TrimSpace(args.Parse.InFile) == "-" {
|
||||
buf = new(bytes.Buffer)
|
||||
if _, err = io.Copy(buf, os.Stdin); err != nil {
|
||||
@ -102,26 +125,32 @@ func main() {
|
||||
}
|
||||
cmnArgs = args.SplitHost.common
|
||||
splitter = &netsplit.HostSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
NumberHosts: args.SplitHost.Hosts,
|
||||
Strict: args.SplitHost.Strict,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
noStrict = !args.SplitHost.Strict
|
||||
strictErr = netsplit.ErrBadNumHosts
|
||||
case "split-nets":
|
||||
if err = validate.Struct(args.SplitSubnets); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
cmnArgs = args.SplitSubnets.common
|
||||
splitter = &netsplit.SubnetSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
Strict: args.SplitSubnets.Strict,
|
||||
NumberSubnets: args.SplitSubnets.NumNets,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
noStrict = !args.SplitSubnets.Strict
|
||||
strictErr = netsplit.ErrNoNetSpace
|
||||
case "split-cidr":
|
||||
if err = validate.Struct(args.SplitCIDR); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
cmnArgs = args.SplitCIDR.common
|
||||
splitter = &netsplit.CIDRSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
PrefixLength: args.SplitCIDR.Prefix,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
case "vlsm":
|
||||
if err = validate.Struct(args.VLSM); err != nil {
|
||||
@ -129,9 +158,9 @@ func main() {
|
||||
}
|
||||
cmnArgs = args.VLSM.common
|
||||
splitter = &netsplit.VLSMSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
Ascending: args.VLSM.Asc,
|
||||
PrefixLengths: args.VLSM.Sizes,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
}
|
||||
if origPfx, err = netip.ParsePrefix(cmnArgs.Network.Network); err != nil {
|
||||
@ -147,8 +176,12 @@ func main() {
|
||||
splitter.SetParent(*pfx)
|
||||
if nets, remaining, err = splitter.Split(); err != nil {
|
||||
if errors.As(err, &splitErr) {
|
||||
printSplitErr(splitErr)
|
||||
os.Exit(1)
|
||||
if noStrict && errors.Is(splitErr.Wrapped, strictErr) {
|
||||
err = nil
|
||||
} else {
|
||||
printSplitErr(splitErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -1,8 +1,134 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
`math/big`
|
||||
`net`
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// subnetResult is only used for human/"pretty" printing.
|
||||
type subnetResult netip.Prefix
|
||||
|
||||
type tableOpts struct {
|
||||
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."`
|
||||
NoIpv6 bool `short:"4" long:"ipv4" description:"Only show IPv4 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.
|
||||
type tableLegacy4 struct {
|
||||
Class string
|
||||
CIDR string
|
||||
Start string
|
||||
End string
|
||||
NetStart netip.Addr `render:"-"`
|
||||
NetEnd netip.Addr `render:"-"`
|
||||
NetCIDR netip.Prefix `render:"-"`
|
||||
}
|
||||
|
||||
// tableLegacy4Sizer is used to size tableLegacy4 entries.
|
||||
type tableLegacy4Sizer struct {
|
||||
Class uint8
|
||||
CIDR uint8
|
||||
Start uint8
|
||||
End uint8
|
||||
}
|
||||
|
||||
// tableFormatter is used for "rendering" table output.
|
||||
type tableFormatter struct {
|
||||
// Headers...
|
||||
// First char, first line
|
||||
TopLeftHdr string
|
||||
// Char to fill between TopLeftHdr and TopColSepHdr.
|
||||
TopFillHdr string
|
||||
// Column separator, first line
|
||||
TopColSepHdr string
|
||||
// Last char, first line
|
||||
TopRightHdr string
|
||||
// Column separator for both column separators (title line) and left/right-most lines.
|
||||
ColSepHdr string
|
||||
// First char, last line of header
|
||||
BottomLeftHdr string
|
||||
// Char to fill between BottomLeftHdr and BottomColSepHdr.
|
||||
BottomFillHdr string
|
||||
// Column separator, last line of header
|
||||
BottomColSepHdr string
|
||||
// Last char, last line of header
|
||||
BottomRightHdr string
|
||||
// Rows...
|
||||
// Left-most line (border).
|
||||
Left string
|
||||
// Fill the cell lines for values
|
||||
Fill string
|
||||
// Separate line/cell border columns
|
||||
LineColSep string
|
||||
// "In-between" rows; left-most.
|
||||
LineLeft string
|
||||
// "In-between" rows, right-most.
|
||||
LineRight string
|
||||
// Separate value columns
|
||||
ColSep string
|
||||
// Right-most line (border)
|
||||
Right string
|
||||
// Last lines get special treatment as they are also a border.
|
||||
LastLeft string
|
||||
LastFill string
|
||||
LastSep string
|
||||
LastRight string
|
||||
// This is mostly for experimentation for Plain output, but...
|
||||
SuppressLineSep bool
|
||||
NoUpperTitle bool
|
||||
NoBoldTitle bool
|
||||
}
|
||||
|
10
go.mod
10
go.mod
@ -5,11 +5,13 @@ go 1.23.2
|
||||
toolchain go1.23.5
|
||||
|
||||
require (
|
||||
github.com/TwiN/go-color v1.4.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
|
||||
github.com/projectdiscovery/mapcidr v1.1.34
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/mod v0.22.0
|
||||
r00t2.io/sysutils v1.12.0
|
||||
)
|
||||
|
||||
@ -19,17 +21,17 @@ require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||
github.com/projectdiscovery/utils v0.0.85 // indirect
|
||||
github.com/projectdiscovery/utils v0.4.8 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
r00t2.io/goutils v1.7.1 // indirect
|
||||
r00t2.io/goutils v1.7.2 // indirect
|
||||
)
|
||||
|
20
go.sum
20
go.sum
@ -1,3 +1,5 @@
|
||||
github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc=
|
||||
github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
@ -18,14 +20,14 @@ github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1
|
||||
github.com/goccy/go-yaml v1.15.16 h1:PMTVcGI9uNPIn7KLs0H7KC1rE+51yPl5YNh4i8rGuRA=
|
||||
github.com/goccy/go-yaml v1.15.16/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@ -34,8 +36,8 @@ github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k
|
||||
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
|
||||
github.com/projectdiscovery/mapcidr v1.1.34 h1:udr83vQ7oz3kEOwlsU6NC6o08leJzSDQtls1wmXN/kM=
|
||||
github.com/projectdiscovery/mapcidr v1.1.34/go.mod h1:1+1R6OkKSAKtWDXE9RvxXtXPoajXTYX0eiEdkqlhQqQ=
|
||||
github.com/projectdiscovery/utils v0.0.85 h1:JpCVc9GJwJLNHy1MBPmAHJcE6rs7bRv72Trb3u84OHE=
|
||||
github.com/projectdiscovery/utils v0.0.85/go.mod h1:ttiPgS2LmLFd+VRBUdgfLKMMdrF98zX7z5W+K71MX40=
|
||||
github.com/projectdiscovery/utils v0.4.8 h1:/Xd38fP8xc6kifZayjrhcYALenJrjO3sHO7lg+I8ZGk=
|
||||
github.com/projectdiscovery/utils v0.4.8/go.mod h1:S314NzLcXVCbLbwYCoorAJYcnZEwv7Uhw2d3aF5fJ4s=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
@ -44,6 +46,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
@ -56,8 +60,8 @@ golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
|
||||
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||
r00t2.io/goutils v1.7.2 h1:dJ+pzY/U1yVi2V6eKoxe/4roM+Tb3d0umMEL9Dx4+Lw=
|
||||
r00t2.io/goutils v1.7.2/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||
r00t2.io/sysutils v1.12.0 h1:Ce3qUOyLixE1ZtFT/+SVwOT5kSkzg5+l1VloGeGugrU=
|
||||
r00t2.io/sysutils v1.12.0/go.mod h1:bNTKNBk9MnUhj9coG9JBNicSi5FrtJHEM645um85pyw=
|
||||
|
@ -4,6 +4,7 @@ import "errors"
|
||||
|
||||
var (
|
||||
ErrBadBoundary error = errors.New("subnet does not align on bit boundary")
|
||||
ErrBadNumHosts error = errors.New("bad number of hosts; cannot split into prefix exactly")
|
||||
ErrBadPrefix error = errors.New("prefix is invalid")
|
||||
ErrBadPrefixLen error = errors.New("prefix length exceeds maximum possible for prefix's inet family")
|
||||
ErrBadSplitter error = errors.New("invalid or unknown splitter when containing")
|
||||
|
@ -4,11 +4,12 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/goccy/go-yaml"
|
||||
"go4.org/netipx"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -96,9 +97,9 @@ Set as 0 or `segSep` to an empty string to do no string segmentation.
|
||||
func AddrFmt(ip netip.Addr, f, sep, segSep string, every, everySeg uint) (s string) {
|
||||
|
||||
var numSegs int
|
||||
var doSep bool = every > 0
|
||||
var fs string = "%" + f
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var doSep = every > 0
|
||||
var fs = "%" + f
|
||||
var sb = new(strings.Builder)
|
||||
|
||||
if ip.IsUnspecified() || !ip.IsValid() {
|
||||
return
|
||||
@ -152,7 +153,7 @@ func AddrInvert(ip netip.Addr) (inverted netip.Addr) {
|
||||
func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSet, splitter NetSplitter) (s *StructuredResults, err error) {
|
||||
|
||||
var rem []netip.Prefix
|
||||
var sr StructuredResults = StructuredResults{
|
||||
var sr = StructuredResults{
|
||||
Original: origPfx,
|
||||
}
|
||||
|
||||
@ -243,9 +244,9 @@ Its parameters hold the same significance as in AddrFmt.
|
||||
func MaskFmt(mask net.IPMask, f, sep, segSep string, every, everySeg uint) (s string) {
|
||||
|
||||
var numSegs int
|
||||
var doSep bool = every > 0
|
||||
var fs string = "%" + f
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var doSep = every > 0
|
||||
var fs = "%" + f
|
||||
var sb = new(strings.Builder)
|
||||
|
||||
if mask == nil || len(mask) == 0 {
|
||||
return
|
||||
|
@ -1,14 +1,58 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"go4.org/netipx"
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
// Split splits the network defined in a CIDRSplitter alongside its configuration and performs the subnetting.
|
||||
/*
|
||||
Split splits the network defined in a CIDRSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy attempts to split a network into subnets of a single uniform explicit size.
|
||||
*/
|
||||
func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
// TODO
|
||||
var ok bool
|
||||
var base netip.Prefix
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
|
||||
if c == nil || c.PrefixLength == 0 || c.BaseSplitter == nil || c.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if base, ok = netipx.FromStdIPNet(c.network); !ok {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
if !base.IsValid() {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
|
||||
if c.PrefixLength > uint8(base.Bits()) {
|
||||
err = ErrBigPrefix
|
||||
return
|
||||
}
|
||||
|
||||
ipsb.AddPrefix(base)
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if sub, remaining, ok = remaining.RemoveFreePrefix(c.PrefixLength); !ok {
|
||||
if !sub.IsValid() {
|
||||
// No error; it's literally impossible since we network on boundaries.
|
||||
// We just hit the end of the prefix.
|
||||
break
|
||||
}
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1,14 +1,68 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"go4.org/netipx"
|
||||
`math/big`
|
||||
`net`
|
||||
"net/netip"
|
||||
|
||||
`github.com/projectdiscovery/mapcidr`
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
// Split splits the network defined in a HostSplitter alongside its configuration and performs the subnetting.
|
||||
/*
|
||||
Split splits the network defined in a HostSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy attempts to split the network into subnets of equal number of hosts.
|
||||
|
||||
remaining may or may not be nil depending on if the number of hosts can fit cleanly within equal network sizes on boundaries.
|
||||
|
||||
An ErrBadNumHosts will be returned if the number of hosts does not match the *addressable* range in a prefix.
|
||||
*/
|
||||
func (h *HostSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
// TODO
|
||||
var tgt *big.Int
|
||||
var hosts *big.Int
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var split []*net.IPNet
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
|
||||
if h == nil || h.NumberHosts == 0 || h.BaseSplitter == nil || h.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if split, err = mapcidr.SplitIPNetByNumber(h.network, int(h.NumberHosts)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tgt = big.NewInt(0)
|
||||
tgt.SetUint64(uint64(h.NumberHosts))
|
||||
|
||||
nets = make([]*netip.Prefix, len(split))
|
||||
for idx, n := range split {
|
||||
sub, _ = netipx.FromStdIPNet(n)
|
||||
hosts = mapcidr.CountIPsInCIDR(false, false, n)
|
||||
if hosts == nil || tgt.Cmp(hosts) != 0 {
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrBadNumHosts,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: &sub,
|
||||
RequestedPrefixLen: uint8(sub.Bits()),
|
||||
}
|
||||
ipsb.AddPrefix(sub)
|
||||
} else {
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
}
|
||||
|
||||
nets[idx] = new(netip.Prefix)
|
||||
*nets[idx] = sub
|
||||
}
|
||||
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"go4.org/netipx"
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -1,14 +1,106 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"go4.org/netipx"
|
||||
`net`
|
||||
"net/netip"
|
||||
|
||||
`github.com/projectdiscovery/mapcidr`
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
// Split splits the network defined in a SubnetSplitter alongside its configuration and performs the subnetting.
|
||||
/*
|
||||
Split splits the network defined in a SubnetSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy allows for splitting a network into exactly evenly sized specified number of subnets.
|
||||
|
||||
remaining may or may not be nil depending on if the specified number of subnets fit cleanly into the network boundaries.
|
||||
|
||||
An ErrNoNetSpace error will be returned if subnetting size exhaustion occurs before the specified number of subnets is reached
|
||||
(but nets will be populated and remaining will contain any left over subnets).
|
||||
*/
|
||||
func (s *SubnetSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
// TODO
|
||||
var ok bool
|
||||
var pfxLen int
|
||||
var base netip.Prefix
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var split []*net.IPNet
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
|
||||
if s == nil || s.BaseSplitter == nil || s.network == nil || s.NumberSubnets == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if base, ok = netipx.FromStdIPNet(s.network); !ok {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
if !base.IsValid() {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
|
||||
if split, err = mapcidr.SplitIPNetIntoN(s.network, int(s.NumberSubnets)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, n := range split {
|
||||
if sub, ok = netipx.FromStdIPNet(n); !ok {
|
||||
// We bail early on this error.
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrBadBoundary,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: subPtr,
|
||||
RequestedPrefixLen: 0,
|
||||
}
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
if sub.String() == base.String() {
|
||||
continue
|
||||
}
|
||||
if pfxLen == 0 {
|
||||
pfxLen = sub.Bits()
|
||||
if nets == nil {
|
||||
nets = make([]*netip.Prefix, 0)
|
||||
}
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
} else {
|
||||
if sub.Bits() != pfxLen {
|
||||
if err == nil {
|
||||
// Return this err but don't return early; wait for the populate.
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrNoNetSpace,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: subPtr,
|
||||
RequestedPrefixLen: uint8(pfxLen),
|
||||
}
|
||||
}
|
||||
ipsb.AddPrefix(sub)
|
||||
} else {
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(nets) < int(s.NumberSubnets) {
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrNoNetSpace,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1,12 +1,18 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"go4.org/netipx"
|
||||
"net/netip"
|
||||
"sort"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
// Split splits the network defined in a VLSMSplitter alongside its configuration and performs the subnetting.
|
||||
/*
|
||||
Split splits the network defined in a VLSMSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy allows for multiple subnets of differing sizes to be specified.
|
||||
|
||||
remaining may or may not be nil depending on if all desired subnet sizes fit cleanly into the network boundaries.
|
||||
*/
|
||||
func (v *VLSMSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
var ok bool
|
||||
@ -15,7 +21,7 @@ func (v *VLSMSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
var base netip.Prefix
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
var ipsb = new(netipx.IPSetBuilder)
|
||||
|
||||
if err = ValidateSizes(v.network, v.PrefixLengths...); err != nil {
|
||||
return
|
||||
|
3
netsplit/init.go
Normal file
3
netsplit/init.go
Normal file
@ -0,0 +1,3 @@
|
||||
package netsplit
|
||||
|
||||
// TODO?
|
@ -2,9 +2,10 @@ package netsplit
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"go4.org/netipx"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
// SplitErr is used to wrap an error with context surrounding when/how that error was encountered.
|
||||
@ -48,7 +49,9 @@ It attempts to evenly distribute addresses amoungs subnets.
|
||||
*/
|
||||
type HostSplitter struct {
|
||||
// NumberHosts is the number of hosts to be placed in each subnet to split out.
|
||||
NumberHosts uint `json:"hosts" xml:"hosts,attr" yaml:"Number of Hosts Per Subnet"`
|
||||
NumberHosts uint `json:"hosts" xml:"hosts,attr" yaml:"Number of Hosts Per Subnet"`
|
||||
// Strict, if true, will return an error from Split if the network cannot split into subnets of NumberHosts-addressable networks exactly.
|
||||
Strict bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Hosts Per Subnet"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
@ -59,6 +62,8 @@ as cleanly as poossible.
|
||||
type SubnetSplitter struct {
|
||||
// NumberSubnets indicates the number of subnets to split the network into.
|
||||
NumberSubnets uint `json:"nets" xml:"nets,attr" yaml:"Number of Target Subnets"`
|
||||
// Strict, if true, will return an error from Split if the network sizes cannot split into equally-sized networks.
|
||||
Strict bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Subnet Sizes"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
|
63
version/consts.go
Normal file
63
version/consts.go
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
* Copyright (c) 2024, NetFire™ <https://netfire.com/>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
/*
|
||||
These variables are automatically handled by the build script.
|
||||
|
||||
DO NOT MODIFY THESE VARIABLES.
|
||||
Refer to /build.sh for how these are generated at build time and populated.
|
||||
*/
|
||||
var (
|
||||
sourceControl string = "git"
|
||||
repoUri string = "(unknown)"
|
||||
version string = "(unknown)"
|
||||
commitHash string
|
||||
commitShort string
|
||||
numCommitsAfterTag string
|
||||
isDirty string
|
||||
buildTime string
|
||||
buildUser string
|
||||
buildSudoUser string
|
||||
buildHost string
|
||||
)
|
||||
|
||||
var (
|
||||
patchRe *regexp.Regexp = regexp.MustCompile(`^(?P<patch>[0-9+])(?P<pre>-[0-9A-Za-z.-]+)?(?P<build>\+[0-9A-Za-z.-]+)?$`)
|
||||
patchReIsolated *regexp.Regexp = regexp.MustCompile(`^([0-9]+)(?:[-+](.*)?)?$`)
|
||||
)
|
||||
|
||||
// Ver is populated by main() from the build script and used in other places.
|
||||
var Ver *BuildInfo
|
180
version/funcs.go
Normal file
180
version/funcs.go
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
* Copyright (c) 2024, NetFire™ <https://netfire.com/>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`runtime`
|
||||
`strconv`
|
||||
`strings`
|
||||
`time`
|
||||
|
||||
`golang.org/x/mod/semver`
|
||||
)
|
||||
|
||||
// Version returns the build information. See build.sh.
|
||||
func Version() (b *BuildInfo, err error) {
|
||||
|
||||
var n int
|
||||
var s string
|
||||
var sb strings.Builder
|
||||
var ok bool
|
||||
var canonical string
|
||||
var build strings.Builder
|
||||
// Why a map?
|
||||
// I forget but I had a reason for it once upon a time.
|
||||
// If you remove it and this func breaks, now you know why.
|
||||
// TODO: how much of this can be replaced by (runtime/debug).ReadBuildInfo()?
|
||||
var raw map[string]string = map[string]string{
|
||||
"repoUri": repoUri,
|
||||
"sourceControl": sourceControl,
|
||||
"tag": version,
|
||||
"hash": commitHash,
|
||||
"shortHash": commitShort,
|
||||
"postTagCommits": numCommitsAfterTag,
|
||||
"dirty": isDirty,
|
||||
"time": buildTime,
|
||||
"user": buildUser,
|
||||
"sudoUser": buildSudoUser,
|
||||
"host": buildHost,
|
||||
}
|
||||
var i BuildInfo = BuildInfo{
|
||||
SourceControl: raw["sourceControl"],
|
||||
GoVersion: runtime.Version(),
|
||||
RepoURI: raw["repoUri"],
|
||||
TagVersion: raw["tag"],
|
||||
// PostTagCommits: 0,
|
||||
CommitHash: raw["hash"],
|
||||
CommitId: raw["shortHash"],
|
||||
BuildUser: raw["user"],
|
||||
RealBuildUser: raw["sudoUser"],
|
||||
// BuildTime: time.Time{},
|
||||
BuildHost: raw["host"],
|
||||
Dirty: false,
|
||||
isDefined: false,
|
||||
raw: raw,
|
||||
}
|
||||
|
||||
if s, ok = raw["postTagCommits"]; ok && strings.TrimSpace(s) != "" {
|
||||
if n, err = strconv.Atoi(s); err == nil {
|
||||
i.PostTagCommits = uint(n)
|
||||
}
|
||||
}
|
||||
|
||||
if s, ok = raw["time"]; ok && strings.TrimSpace(s) != "" {
|
||||
if n, err = strconv.Atoi(s); err == nil {
|
||||
i.BuildTime = time.Unix(int64(n), 0).UTC()
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(raw["dirty"]) {
|
||||
case "1":
|
||||
i.Dirty = true
|
||||
case "0", "":
|
||||
i.Dirty = false
|
||||
}
|
||||
|
||||
// Build the short form. We use this for both BuildInfo.short and BuildInfo.verSplit.
|
||||
if i.TagVersion == "" {
|
||||
sb.WriteString(i.SourceControl)
|
||||
} else {
|
||||
sb.WriteString(i.TagVersion)
|
||||
}
|
||||
/*
|
||||
Now the mess. In order to conform to SemVer 2.0 (the spec this code targets):
|
||||
|
||||
1.) MAJOR.
|
||||
2.) MINOR.
|
||||
3.) PATCH
|
||||
4.) -PRERELEASE (OPTIONAL)
|
||||
(git commit, if building against a commit made past 1-3. Always included if untagged.)
|
||||
5.) +BUILDINFO (OPTIONAL)
|
||||
("+x[.y]", where x is # of commits past 4, or tag commit if 4 is empty. 0 is valid.
|
||||
y is optional, and is the string "dirty" if it is a "dirty" build - that is, uncommitted/unstaged changes.
|
||||
if x and y would be 0 and empty, respectively, then 5 is not included.)
|
||||
|
||||
1-3 are already written, or the source control software used if not.
|
||||
|
||||
Technically 4 and 5 are only included if 3 is present. We force patch to 0 if it's a tagged release and patch isn't present --
|
||||
so this is not relevant.
|
||||
*/
|
||||
// PRERELEASE
|
||||
if i.TagVersion == "" || i.PostTagCommits > 0 {
|
||||
// We use the full commit hash for git versions, short identifier for tagged releases.
|
||||
if i.TagVersion == "" {
|
||||
i.Pre = i.CommitHash
|
||||
} else {
|
||||
i.Pre = i.CommitId
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("-%v", i.Pre))
|
||||
}
|
||||
// BUILD
|
||||
if i.PostTagCommits > 0 || i.Dirty {
|
||||
build.WriteString(strconv.Itoa(int(i.PostTagCommits)))
|
||||
if i.Dirty {
|
||||
build.WriteString(".dirty")
|
||||
}
|
||||
i.Build = build.String()
|
||||
sb.WriteString(fmt.Sprintf("+%v", i.Build))
|
||||
}
|
||||
|
||||
i.short = sb.String()
|
||||
if semver.IsValid(i.short) {
|
||||
// DON'T DO THIS. It strips the prerelease and build info.
|
||||
// i.short = semver.Canonical(i.short)
|
||||
// Do this instead.
|
||||
canonical = semver.Canonical(i.short)
|
||||
// Numeric versions...
|
||||
if n, err = strconv.Atoi(strings.TrimPrefix(semver.Major(canonical), "v")); err != nil {
|
||||
err = nil
|
||||
} else {
|
||||
i.Major = uint(n)
|
||||
}
|
||||
if n, err = strconv.Atoi(strings.Split(semver.MajorMinor(canonical), ".")[1]); err != nil {
|
||||
err = nil
|
||||
} else {
|
||||
i.Minor = uint(n)
|
||||
}
|
||||
if n, err = strconv.Atoi(patchReIsolated.FindStringSubmatch(strings.Split(canonical, ".")[2])[1]); err != nil {
|
||||
err = nil
|
||||
} else {
|
||||
i.Patch = uint(n)
|
||||
}
|
||||
// The other tag assignments were performed above.
|
||||
}
|
||||
// The default is 0 for the numerics, so no big deal.
|
||||
|
||||
i.isDefined = true
|
||||
|
||||
b = &i
|
||||
|
||||
return
|
||||
}
|
133
version/funcs_buildinfo.go
Normal file
133
version/funcs_buildinfo.go
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
* Copyright (c) 2024, NetFire™ <https://netfire.com/>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`strings`
|
||||
|
||||
`golang.org/x/mod/semver`
|
||||
)
|
||||
|
||||
// Detail returns a multiline string containing every possible piece of information we collect.
|
||||
func (b *BuildInfo) Detail() (ver string) {
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%v\n\n", b.short))
|
||||
sb.WriteString(fmt.Sprintf("====\nSource Control: %v\nRepo URI: %v\n", b.SourceControl, b.RepoURI))
|
||||
if b.TagVersion != "" {
|
||||
if b.PostTagCommits > 0 {
|
||||
sb.WriteString(fmt.Sprintf("Version Base: %v\nCommit Hash: %v\n", b.TagVersion, b.CommitHash))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("Version: %v\n", b.TagVersion))
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("Version: (Unversioned)\nCommit Hash: %v\n", b.CommitHash))
|
||||
}
|
||||
|
||||
// Post-commits
|
||||
if b.TagVersion != "" {
|
||||
sb.WriteString(fmt.Sprintf("# of Commits Since %v: %v\n", b.TagVersion, b.PostTagCommits))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("# of Commits Since %v: %v\n", b.CommitId, b.PostTagCommits))
|
||||
}
|
||||
|
||||
sb.WriteString("Uncommitted/Unstaged Changes: ")
|
||||
if b.Dirty {
|
||||
sb.WriteString("yes (dirty/monkeypatched build)\n")
|
||||
} else {
|
||||
sb.WriteString("no (clean build)\n")
|
||||
}
|
||||
|
||||
if b.TagVersion != "" {
|
||||
sb.WriteString(
|
||||
fmt.Sprintf(
|
||||
"====\nMajor: %v\nMinor: %v\nPatch: %v\n",
|
||||
b.Major, b.Minor, b.Patch,
|
||||
),
|
||||
)
|
||||
}
|
||||
sb.WriteString("====\n")
|
||||
sb.WriteString(b.Meta())
|
||||
|
||||
ver = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Short returns a uniquely identifiable version string.
|
||||
func (b *BuildInfo) Short() (ver string) {
|
||||
|
||||
ver = b.short
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Meta returns the build/compile-time info.
|
||||
func (b *BuildInfo) Meta() (meta string) {
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
if b.RealBuildUser != b.BuildUser && b.RealBuildUser != "" {
|
||||
sb.WriteString(fmt.Sprintf("Real Build User: %v\n", b.RealBuildUser))
|
||||
sb.WriteString(fmt.Sprintf("Sudo Build User: %v\n", b.BuildUser))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("Build User: %v\n", b.BuildUser))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("Build Time: %v\nBuild Host: %v\nGo Version: %v\n", b.BuildTime, b.BuildHost, b.GoVersion))
|
||||
|
||||
meta = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// getReMap gets a regex map of map[pattern]match.
|
||||
func (b *BuildInfo) getReMap() (matches map[string]string) {
|
||||
|
||||
var s string = b.Short()
|
||||
var sections []string
|
||||
|
||||
if !semver.IsValid(s) {
|
||||
return
|
||||
}
|
||||
|
||||
sections = strings.Split(s, ".")
|
||||
|
||||
// The split should contain everything in the third element.
|
||||
// Or, if using a "simplified" semver, the last element.
|
||||
matches = make(map[string]string)
|
||||
for idx, str := range patchRe.FindStringSubmatch(sections[len(sections)-1]) {
|
||||
matches[patchRe.SubexpNames()[idx]] = str
|
||||
}
|
||||
|
||||
return
|
||||
}
|
85
version/types.go
Normal file
85
version/types.go
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* BSD 3-Clause License
|
||||
* Copyright (c) 2024, NetFire™ <https://netfire.com/>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package version
|
||||
|
||||
import (
|
||||
`time`
|
||||
)
|
||||
|
||||
// BuildInfo contains nativized version information.
|
||||
type BuildInfo struct {
|
||||
// RepoURI is where the source is from.
|
||||
RepoURI string
|
||||
// GoVersion is the version of the Go compiler used.
|
||||
GoVersion string
|
||||
// TagVersion is the most recent tag name on the current branch.
|
||||
TagVersion string
|
||||
// PostTagCommits is the number of commits after BuildInfo.TagVersion's commit on the current branch.
|
||||
PostTagCommits uint
|
||||
// CommitHash is the full commit hash.
|
||||
CommitHash string
|
||||
// CommitId is the "short" version of BuildInfo.CommitHash.
|
||||
CommitId string
|
||||
// BuildUser is the user the program was compiled under.
|
||||
BuildUser string
|
||||
// If compiled under sudo, BuildInfo.RealBuildUser is the user that called sudo.
|
||||
RealBuildUser string
|
||||
// BuildTime is the time and date of the program's build time.
|
||||
BuildTime time.Time
|
||||
// BuildHost is the host the binary was compiled on.
|
||||
BuildHost string
|
||||
// Dirty specifies if the source was "dirty" (uncommitted/unstaged etc. files) at the time of compilation.
|
||||
Dirty bool
|
||||
// SourceControl is the source control version used. Only relevant if not a "clean" build or untagged.
|
||||
SourceControl string
|
||||
// Major is the major version, expressed as an uint per spec.
|
||||
Major uint
|
||||
// Minor is the minor version, expressed as an uint per spec.
|
||||
Minor uint
|
||||
// Patch is the patch version, expressed as an uint per spec.
|
||||
Patch uint
|
||||
// Pre
|
||||
Pre string
|
||||
// Build
|
||||
Build string
|
||||
// isDefined specifies if this version was retrieved from the built-in values.
|
||||
isDefined bool
|
||||
// raw is the raw variable values.
|
||||
raw map[string]string
|
||||
/*
|
||||
verSplit is a slice of []string{Major, Minor, Patch, PreRelease, Build}
|
||||
|
||||
If using an actual point release, PreRelease and Build are probably blank.
|
||||
*/
|
||||
verSplit [5]string
|
||||
// short is the condensed version of verSplit.
|
||||
short string
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user