almost done ackshually
This commit is contained in:
64
cmd/subnetter/args.go
Normal file
64
cmd/subnetter/args.go
Normal file
@@ -0,0 +1,64 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
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)."`
|
||||
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."`
|
||||
}
|
||||
|
||||
type common struct {
|
||||
outputOpts
|
||||
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
|
||||
AllowHostNet bool `short:"H" long:"allow-host" description:"If specified, do not warn about host bits. Host bits are always removed for subnetting (as otherwise there would be errors); this is only used only for output."`
|
||||
Network Net `positional-args:"yes" required:"true" description:"The network to be split/subnetted." validate:"required"`
|
||||
}
|
||||
|
||||
type ParseArgs struct {
|
||||
outputOpts
|
||||
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
|
||||
AllowHostNet bool `short:"H" long:"allow-host" description:"If specified, do not warn about host bits."`
|
||||
InFile string `short:"i" long:"input" default:"-" description:"Input file to parse. Default is '-' (for STDIN)." required:"true" validate:"required,filepath|eq=-"`
|
||||
}
|
||||
|
||||
type SplitCIDRArgs struct {
|
||||
Prefix uint8 `short:"s" long:"size" required:"true" description:"Prefix length/network size in bits (as CIDR number)." validate:"required"`
|
||||
common
|
||||
}
|
||||
|
||||
type SplitHostArgs struct {
|
||||
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
|
||||
common
|
||||
}
|
||||
|
||||
type SplitSubnetArgs struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
type VLSMArgs struct {
|
||||
Asc bool `short:"a" long:"asc" description:"If specified, place smaller networks (larger prefixes) at the beginning. You almost assuredly do not want to do this."`
|
||||
Sizes []uint8 `short:"s" long:"size" required:"true" description:"Prefix lengths. May be specified multiple times." validate:"required"`
|
||||
common
|
||||
}
|
||||
|
||||
type Net struct {
|
||||
Network string `positional-arg-name:"<network>/<prefix>" description:"network address with prefix. Can be IPv4 or IPv6." validate:"required,cidr"`
|
||||
}
|
||||
18
cmd/subnetter/consts.go
Normal file
18
cmd/subnetter/consts.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
args *Args = new(Args)
|
||||
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
)
|
||||
|
||||
var (
|
||||
sectSepCnt int = 48
|
||||
sectSep1 string = strings.Repeat("=", sectSepCnt)
|
||||
sectSep2 string = strings.Repeat("-", sectSepCnt)
|
||||
sectSep3 string = strings.Repeat(".", sectSepCnt)
|
||||
)
|
||||
438
cmd/subnetter/funcs.go
Normal file
438
cmd/subnetter/funcs.go
Normal file
@@ -0,0 +1,438 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"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"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
if pfx == nil {
|
||||
fmt.Fprintf(sb, "%s%s:\n%sAddress:\t(N/A)\n", pre, label, pre2)
|
||||
if verb >= 2 {
|
||||
fmt.Fprintf(sb, "%sExpanded:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sCompressed:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sHex:\t\t(N/A)\n", pre2)
|
||||
}
|
||||
if verb >= 3 {
|
||||
fmt.Fprintf(sb, "%sDecimal:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sBinary:\t\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sOctal:\t\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sUnicast:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sILM:\t\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sLLM:\t\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sLLU:\t\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sLoopback:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sMulticast:\t(N/A)\n", pre2)
|
||||
fmt.Fprintf(sb, "%sPrivate:\t(N/A)\n", pre2)
|
||||
}
|
||||
out = sb.String()
|
||||
return
|
||||
}
|
||||
|
||||
if pfx.Addr().Is4() {
|
||||
maskEvery = 1
|
||||
} else {
|
||||
maskEvery = 2
|
||||
}
|
||||
|
||||
fmt.Fprintf(sb,
|
||||
"%s%s:\n%sAddress:\t%s\n",
|
||||
pre, label, pre2, pfx.Addr().String(),
|
||||
)
|
||||
if verb >= 2 {
|
||||
fmt.Fprintf(sb, "%sExpanded:\t%s\n", pre2, netsplit.AddrExpand(pfx.Addr()))
|
||||
fmt.Fprintf(sb, "%sCompressed:\t%s\n", pre2, netsplit.AddrCompress(pfx))
|
||||
fmt.Fprintf(sb,
|
||||
"%sHex:\t\t0x%s\n",
|
||||
pre2, netsplit.AddrFmt(pfx.Addr(), "02x", "", "", 0, 0),
|
||||
)
|
||||
}
|
||||
if verb >= 3 {
|
||||
fmt.Fprintf(sb, "%sDecimal:\t%d\n", pre2, binary.BigEndian.Uint32(pfx.Addr().AsSlice()))
|
||||
fmt.Fprintf(sb,
|
||||
"%sBinary:\t\t0b%s\n",
|
||||
pre2, netsplit.AddrFmt(
|
||||
pfx.Addr(), "08b", ".", fmt.Sprintf("\n%s\t\t ", pre2), maskEvery, 2,
|
||||
),
|
||||
)
|
||||
fmt.Fprintf(sb,
|
||||
"%sOctal:\t\t0o%s\n",
|
||||
pre2, netsplit.AddrFmt(
|
||||
pfx.Addr(), "03o", ".", fmt.Sprintf("\n%s\t\t ", pre2), 1, 8,
|
||||
),
|
||||
)
|
||||
fmt.Fprintf(sb, "%sUnicast:\t%v\n", pre2, pfx.Addr().IsGlobalUnicast())
|
||||
fmt.Fprintf(sb, "%sILM:\t\t%v\n", pre2, pfx.Addr().IsInterfaceLocalMulticast())
|
||||
fmt.Fprintf(sb, "%sLLM:\t\t%v\n", pre2, pfx.Addr().IsLinkLocalMulticast())
|
||||
fmt.Fprintf(sb, "%sLLU:\t\t%v\n", pre2, pfx.Addr().IsLinkLocalUnicast())
|
||||
fmt.Fprintf(sb, "%sLoopback:\t%v\n", pre2, pfx.Addr().IsLoopback())
|
||||
fmt.Fprintf(sb, "%sMulticast:\t%v\n", pre2, pfx.Addr().IsMulticast())
|
||||
fmt.Fprintf(sb, "%sPrivate:\t%v\n", pre2, pfx.Addr().IsPrivate())
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printMask(label string, pfx netip.Prefix, verb, indent int, indentStr string) (out string) {
|
||||
|
||||
var maskF string
|
||||
var maskSep string
|
||||
var maskEvery uint
|
||||
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)
|
||||
|
||||
if !pfx.IsValid() {
|
||||
return
|
||||
}
|
||||
mask = netipx.PrefixIPNet(pfx).Mask
|
||||
|
||||
if pfx.Addr().Is4() {
|
||||
maskF = "d"
|
||||
maskSep = "."
|
||||
maskEvery = 1
|
||||
// IPv4 *always* reserves last addr for broadcast UNLESS it's a /31 (or /32). RFC 919, RFC 1770, RFC 5735.
|
||||
switch pfx.Bits() {
|
||||
case 32: // Host
|
||||
first = pfx.Masked().Addr()
|
||||
last = pfx.Masked().Addr()
|
||||
case 31: // Point-to-Point
|
||||
first = pfx.Masked().Addr()
|
||||
last = pfx.Masked().Addr().Next()
|
||||
default: // RFC 919, RFC 5735
|
||||
first = pfx.Masked().Addr().Next()
|
||||
last = netipx.PrefixLastIP(pfx.Masked()).Prev()
|
||||
}
|
||||
} else {
|
||||
maskF = "02x"
|
||||
maskSep = ":"
|
||||
maskEvery = 2
|
||||
switch pfx.Bits() {
|
||||
case 128: // Host/Loopback
|
||||
first = pfx.Masked().Addr()
|
||||
last = pfx.Masked().Addr()
|
||||
case 127: // Point-to-Point
|
||||
first = pfx.Masked().Addr()
|
||||
last = pfx.Masked().Addr().Next()
|
||||
case 64:
|
||||
first = pfx.Masked().Addr().Next()
|
||||
// IPv6 only reserves the last address (for EUI-64 reasons) for /64's.
|
||||
last = netipx.PrefixLastIP(pfx.Masked()).Prev()
|
||||
default:
|
||||
first = pfx.Masked().Addr()
|
||||
last = netipx.PrefixLastIP(pfx.Masked())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(sb,
|
||||
"%s%s:\n%sNetmask:\t%s\n",
|
||||
pre, label, pre2, netsplit.MaskFmt(mask, maskF, maskSep, "", maskEvery, 0),
|
||||
)
|
||||
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())
|
||||
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())
|
||||
}
|
||||
if verb >= 3 {
|
||||
fmt.Fprintf(sb, "%sDecimal:\t%d\n", pre2, binary.BigEndian.Uint32(mask))
|
||||
fmt.Fprintf(sb,
|
||||
"%sBinary:\t\t0b%s\n",
|
||||
pre2, netsplit.MaskFmt(
|
||||
mask, "08b", ".", fmt.Sprintf("\n%s\t\t ", pre2), maskEvery, 2,
|
||||
),
|
||||
)
|
||||
fmt.Fprintf(sb,
|
||||
"%sOctal:\t\t0o%s\n",
|
||||
pre2, netsplit.MaskFmt(
|
||||
mask, "03o", ".", fmt.Sprintf("\n%s\t\t ", pre2), 1, 8,
|
||||
),
|
||||
)
|
||||
|
||||
// Inverted mask
|
||||
mask = netsplit.MaskInvert(mask)
|
||||
fmt.Fprintf(sb,
|
||||
"%sInverted Mask (\"Cisco Wildcard\"):\n%sNetmask:\t%s\n",
|
||||
pre2, pre3, netsplit.MaskFmt(mask, maskF, maskSep, "", maskEvery, 0),
|
||||
)
|
||||
fmt.Fprintf(sb, "%sBits:\t\t%d\n", pre3, pfx.Bits())
|
||||
fmt.Fprintf(sb, "%sExpanded:\t%s\n", pre3, netsplit.MaskExpand(mask, pfx.Addr().Is6()))
|
||||
fmt.Fprintf(sb, "%sHex:\t\t0x%s\n", pre3, mask.String())
|
||||
fmt.Fprintf(sb, "%sDecimal:\t%d\n", pre3, binary.BigEndian.Uint32(mask))
|
||||
fmt.Fprintf(sb,
|
||||
"%sBinary:\t\t0b%s\n",
|
||||
pre3, netsplit.MaskFmt(
|
||||
mask, "08b", ".", fmt.Sprintf("\n%s\t\t ", pre3), maskEvery, 2,
|
||||
),
|
||||
)
|
||||
fmt.Fprintf(sb,
|
||||
"%sOctal:\t\t0o%s\n",
|
||||
pre3, netsplit.MaskFmt(
|
||||
mask, "03o", ".", fmt.Sprintf("\n%s\t\t ", pre3), 1, 8,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, remaining *netipx.IPSet, args *common, splitter netsplit.NetSplitter) (err error) {
|
||||
|
||||
var b []byte
|
||||
var netsLen uint
|
||||
var remLen uint
|
||||
var buf *bytes.Buffer
|
||||
var masked netip.Prefix
|
||||
var remPfxs []*netip.Prefix
|
||||
var invertedMask net.IPMask
|
||||
var res *netsplit.StructuredResults
|
||||
var verb int = -1
|
||||
|
||||
if orig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if args == nil {
|
||||
args = &common{
|
||||
outputOpts: outputOpts{
|
||||
Seperator: "\n",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if args.outputOpts.Verbose != nil {
|
||||
verb = 0
|
||||
for _, i := range args.outputOpts.Verbose {
|
||||
if i {
|
||||
verb++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if nets != nil && len(nets) > 0 {
|
||||
netsLen = uint(len(nets))
|
||||
}
|
||||
if remaining != nil {
|
||||
remLen = uint(len(remaining.Prefixes()))
|
||||
remPfxs = make([]*netip.Prefix, remLen)
|
||||
for idx, p := range remaining.Prefixes() {
|
||||
remPfxs[idx] = new(netip.Prefix)
|
||||
*remPfxs[idx] = p
|
||||
}
|
||||
}
|
||||
|
||||
masked = orig.Masked()
|
||||
|
||||
invertedMask = netsplit.MaskInvert(origNet.Mask)
|
||||
|
||||
if verb < 0 {
|
||||
verb = -1
|
||||
}
|
||||
|
||||
if args.outputOpts.Fmt == "pretty" {
|
||||
// "Human"-formatted
|
||||
|
||||
// Header
|
||||
if !args.AllowHostNet && (orig.String() != origNet.String()) {
|
||||
// The host bits were removed. Warn to STDERR.
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"!! WARNING: !!"+
|
||||
"\n\tOriginal prefix '%s' had host bits set; converted to actual network boundary '%s'.\n",
|
||||
orig.String(), origNet.String(),
|
||||
)
|
||||
}
|
||||
if verb >= 1 {
|
||||
fmt.Printf(
|
||||
"= %s =\n%d Subnets, %d Remaining/Left Over/Unallocated.\n",
|
||||
origNet.String(), netsLen, remLen,
|
||||
)
|
||||
fmt.Println(sectSep1)
|
||||
|
||||
// Host (if specified)
|
||||
if orig.String() != origNet.String() {
|
||||
fmt.Print(printHostPrefix("Host", orig, verb, 0, "\t"))
|
||||
} else {
|
||||
fmt.Print(printHostPrefix("Host", nil, verb, 0, "\t"))
|
||||
}
|
||||
fmt.Println(sectSep2)
|
||||
|
||||
// Net mask
|
||||
fmt.Print(printMask("Mask", orig.Masked(), verb, 0, "\t"))
|
||||
fmt.Println(sectSep2)
|
||||
|
||||
// network address
|
||||
fmt.Print(printHostPrefix("Network", &masked, verb, 0, "\t"))
|
||||
|
||||
fmt.Println(sectSep1)
|
||||
}
|
||||
|
||||
// Allocations
|
||||
if verb >= 1 {
|
||||
fmt.Println()
|
||||
fmt.Println(sectSep1)
|
||||
fmt.Println("Subnets:")
|
||||
}
|
||||
if netsLen == 0 {
|
||||
if verb >= 1 {
|
||||
fmt.Println("(Subnetting not possible.)")
|
||||
}
|
||||
} else {
|
||||
for _, n := range nets {
|
||||
fmt.Print(resFromPfx(n).pretty(verb, 1, args.outputOpts.Seperator, "\t", false))
|
||||
}
|
||||
}
|
||||
if verb >= 1 {
|
||||
fmt.Println(sectSep1)
|
||||
}
|
||||
|
||||
// Remaining
|
||||
if !args.outputOpts.SuppressRemaining {
|
||||
if verb >= 1 {
|
||||
fmt.Println()
|
||||
fmt.Println(sectSep1)
|
||||
fmt.Println("Remaining/Left Over/Unallocated:")
|
||||
}
|
||||
if remLen == 0 {
|
||||
if verb >= 1 {
|
||||
fmt.Println("(No network space left over/unallocated.)")
|
||||
}
|
||||
} else {
|
||||
if remaining == nil {
|
||||
// This will never, ever fire; it's here to make IDEs stop being dumb and complaining.
|
||||
return
|
||||
}
|
||||
for _, n := range remaining.Prefixes() {
|
||||
fmt.Print(resFromPfx(&n).pretty(verb, 1, args.outputOpts.Seperator, "\t", true))
|
||||
}
|
||||
}
|
||||
if verb >= 1 {
|
||||
fmt.Println(sectSep1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf = new(bytes.Buffer)
|
||||
// TODO: data-formatted/structured output
|
||||
if res, err = netsplit.Contain(orig, nets, remaining, splitter); err != nil {
|
||||
return
|
||||
}
|
||||
switch strings.ToLower(args.outputOpts.Fmt) {
|
||||
case "json":
|
||||
if b, err = json.MarshalIndent(res, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
buf.Write(b)
|
||||
case "xml":
|
||||
fmt.Fprintf(
|
||||
buf,
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`+
|
||||
"<!--\n"+
|
||||
" Generated by subnetter.\n"+
|
||||
" %s\n"+
|
||||
"-->\n",
|
||||
time.Now().String(),
|
||||
)
|
||||
if b, err = xml.MarshalIndent(res, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
buf.Write(b)
|
||||
case "yml":
|
||||
fmt.Fprintf(
|
||||
buf,
|
||||
"# Generated by subnetter.\n"+
|
||||
"# %s\n\n",
|
||||
time.Now().String(),
|
||||
)
|
||||
if b, err = yaml.Marshal(res); err != nil {
|
||||
return
|
||||
}
|
||||
buf.Write(b)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(os.Stdout, buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ = b
|
||||
_ = invertedMask
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printSplitErr(e *netsplit.SplitErr) {
|
||||
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
|
||||
os.Stderr.WriteString("\n!! ERROR !!!\n")
|
||||
|
||||
os.Stderr.WriteString("\t" + e.Wrapped.Error() + "\n")
|
||||
os.Stderr.WriteString("\nnetwork Iteration Details\n(when error was encountered):\n\n")
|
||||
if e.Nets == nil {
|
||||
os.Stderr.WriteString("Nets:\t\t\t(N/A)\n")
|
||||
} else {
|
||||
os.Stderr.WriteString("Nets:\n")
|
||||
for _, n := range e.Nets {
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", n.String())
|
||||
}
|
||||
}
|
||||
if e.Remaining == nil {
|
||||
os.Stderr.WriteString("Remaining:\t\t(N/A)\n")
|
||||
} else {
|
||||
os.Stderr.WriteString("Remaining:\n")
|
||||
for _, n := range e.Remaining.Prefixes() {
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", n.String())
|
||||
}
|
||||
}
|
||||
if e.LastSubnet == nil {
|
||||
os.Stderr.WriteString("Last Subnet:\t\t(N/A)")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Last Subnet:\t\t%s\n", e.LastSubnet.String())
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Desired Prefix Length:\t%d\n", e.RequestedPrefixLen)
|
||||
}
|
||||
|
||||
func resFromPfx(pfx *netip.Prefix) (res *subnetResult) {
|
||||
|
||||
var txPfx subnetResult
|
||||
|
||||
if pfx == nil {
|
||||
return
|
||||
}
|
||||
txPfx = subnetResult(*pfx)
|
||||
res = &txPfx
|
||||
|
||||
return
|
||||
}
|
||||
53
cmd/subnetter/funcs_subnetresult.go
Normal file
53
cmd/subnetter/funcs_subnetresult.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *subnetResult) pretty(verb, indent int, sep, indentStr string, isRemaining bool) (out string) {
|
||||
|
||||
var pfx netip.Prefix
|
||||
var bullet string = "+"
|
||||
var sb *strings.Builder = new(strings.Builder)
|
||||
var pre string = strings.Repeat(indentStr, indent)
|
||||
var pre2 string = strings.Repeat(indentStr, indent+1)
|
||||
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
pfx = netip.Prefix(*s)
|
||||
|
||||
if verb < 0 {
|
||||
verb = -1
|
||||
}
|
||||
|
||||
if isRemaining {
|
||||
bullet = "-"
|
||||
}
|
||||
|
||||
if verb <= 0 {
|
||||
sb.WriteString(pfx.String() + sep)
|
||||
out = sb.String()
|
||||
return
|
||||
} else {
|
||||
sb.WriteString(pre + sectSep2 + "\n")
|
||||
sb.WriteString(
|
||||
printMask(
|
||||
fmt.Sprintf("%s %s", bullet, pfx.String()),
|
||||
pfx,
|
||||
verb,
|
||||
indent,
|
||||
indentStr,
|
||||
),
|
||||
)
|
||||
sb.WriteString(pre2 + sectSep3 + "\n")
|
||||
sb.WriteString(printHostPrefix("Network", &pfx, verb, 2, "\t"))
|
||||
sb.WriteString(pre + sectSep2 + "\n")
|
||||
}
|
||||
|
||||
out = sb.String()
|
||||
|
||||
return
|
||||
}
|
||||
161
cmd/subnetter/main.go
Normal file
161
cmd/subnetter/main.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go4.org/netipx"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"subnetter/netsplit"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"r00t2.io/sysutils/paths"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
var b []byte
|
||||
var pfx *net.IPNet
|
||||
var resPfx *netip.Prefix
|
||||
var origPfx netip.Prefix
|
||||
var splitter netsplit.NetSplitter
|
||||
var cmnArgs common
|
||||
var nets []*netip.Prefix
|
||||
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)
|
||||
|
||||
if _, err = parser.Parse(); err != nil {
|
||||
switch flagsErr := err.(type) {
|
||||
case *flags.Error:
|
||||
switch flagsErr.Type {
|
||||
case flags.ErrHelp, flags.ErrCommandRequired, flags.ErrRequired: // These print their relevant messages by themselves.
|
||||
return
|
||||
default:
|
||||
log.Panicln(err)
|
||||
}
|
||||
default:
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
switch parser.Active.Name {
|
||||
case "table":
|
||||
// TODO: print table and exit
|
||||
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 {
|
||||
log.Panicln(err)
|
||||
}
|
||||
b = buf.Bytes()
|
||||
} else {
|
||||
if err = paths.RealPath(&args.Parse.InFile); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if b, err = os.ReadFile(args.Parse.InFile); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
if res, err = netsplit.Parse(b); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if resPfx, nets, remaining, splitter, err = res.Uncontain(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if resPfx != nil {
|
||||
origPfx = *resPfx
|
||||
}
|
||||
pfx = netipx.PrefixIPNet(origPfx.Masked())
|
||||
cmnArgs = common{
|
||||
outputOpts: args.Parse.outputOpts,
|
||||
AllowReserved: args.Parse.AllowReserved,
|
||||
AllowHostNet: args.Parse.AllowHostNet,
|
||||
}
|
||||
if err = printNets(&origPfx, pfx, nets, remaining, &cmnArgs, res.GetSplitter()); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
return
|
||||
default:
|
||||
// Actually subnet (and print results).
|
||||
/*
|
||||
A netsplit.NetSplitter is needed, along with:
|
||||
* prefix
|
||||
* verbosity
|
||||
* disable showing remaining
|
||||
* formatter
|
||||
These are all handily-dandily enclosed in a `common` struct type.
|
||||
*/
|
||||
switch parser.Active.Name {
|
||||
case "split-hosts":
|
||||
if err = validate.Struct(args.SplitHost); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
cmnArgs = args.SplitHost.common
|
||||
splitter = &netsplit.HostSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
NumberHosts: args.SplitHost.Hosts,
|
||||
}
|
||||
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),
|
||||
NumberSubnets: args.SplitSubnets.NumNets,
|
||||
}
|
||||
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,
|
||||
}
|
||||
case "vlsm":
|
||||
if err = validate.Struct(args.VLSM); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
cmnArgs = args.VLSM.common
|
||||
splitter = &netsplit.VLSMSplitter{
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
Ascending: args.VLSM.Asc,
|
||||
PrefixLengths: args.VLSM.Sizes,
|
||||
}
|
||||
}
|
||||
if origPfx, err = netip.ParsePrefix(cmnArgs.Network.Network); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
// This can be a direct conversion. We have to make sure we mask off the host bits to avoid errors, though.
|
||||
/*
|
||||
if _, pfx, err = net.ParseCIDR(cmnArgs.network.network); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
*/
|
||||
pfx = netipx.PrefixIPNet(origPfx.Masked())
|
||||
splitter.SetParent(*pfx)
|
||||
if nets, remaining, err = splitter.Split(); err != nil {
|
||||
if errors.As(err, &splitErr) {
|
||||
printSplitErr(splitErr)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
if err = printNets(&origPfx, pfx, nets, remaining, &cmnArgs, splitter); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
8
cmd/subnetter/types.go
Normal file
8
cmd/subnetter/types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// subnetResult is only used for human/"pretty" printing.
|
||||
type subnetResult netip.Prefix
|
||||
Reference in New Issue
Block a user