441 lines
11 KiB
Go
441 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
"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 = 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)
|
|
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 = 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
|
|
}
|
|
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(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())
|
|
}
|
|
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 = -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)
|
|
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<!--\n"+
|
|
" Generated by subnetter %s\n"+
|
|
" %s\n"+
|
|
"-->\n",
|
|
version.Ver.Short(), time.Now().String(),
|
|
)
|
|
if b, err = xml.MarshalIndent(res, "", " "); err != nil {
|
|
return
|
|
}
|
|
buf.Write(b)
|
|
case "yml":
|
|
fmt.Fprintf(
|
|
buf,
|
|
"# Generated by subnetter %s\n"+
|
|
"# %s\n\n",
|
|
version.Ver.Short(), 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
|
|
}
|