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
}