525 lines
14 KiB
Go
525 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/goccy/go-yaml"
|
|
"github.com/projectdiscovery/mapcidr"
|
|
"go4.org/netipx"
|
|
"r00t2.io/subnetter/netsplit"
|
|
"r00t2.io/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 resIdx int
|
|
var resPfx netip.Prefix
|
|
var resRec *netsplit.IANAAddrNetResRecord
|
|
var reservedList []*netip.Prefix
|
|
var reserved map[netip.Prefix]*netsplit.IANAAddrNetResRecord
|
|
var res *netsplit.StructuredResults
|
|
var verb = -1
|
|
var fmts []string
|
|
var sectSep1 string
|
|
var sectSep2 string
|
|
// var sectSep3 string
|
|
|
|
if orig == nil {
|
|
return
|
|
}
|
|
|
|
if args == nil {
|
|
args = &common{
|
|
Separator: "\n",
|
|
}
|
|
}
|
|
fmts = sectFmts[args.Plain]
|
|
sectSep1 = fmts[0]
|
|
sectSep2 = fmts[1]
|
|
// sectSep3 = fmts[2]
|
|
|
|
if args.Verbose != nil {
|
|
verb = 0
|
|
for _, i := range args.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.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)
|
|
}
|
|
|
|
// Reservations
|
|
if !args.AllowReserved && verb >= 1 {
|
|
fmt.Println()
|
|
fmt.Println(sectSep1)
|
|
fmt.Println("Reserved Subnets in Selection:")
|
|
if reserved, err = netsplit.CheckReserved(nets, !args.NoRevRecursive, !args.NoRecursive, !args.NoPrivate); err != nil {
|
|
return
|
|
}
|
|
if reserved == nil || len(reserved) == 0 {
|
|
fmt.Println("(No reserved subnets found; good job!)")
|
|
} else {
|
|
reservedList = make([]*netip.Prefix, len(reserved))
|
|
for resPfx, _ = range reserved {
|
|
reservedList[resIdx] = &resPfx
|
|
resIdx++
|
|
}
|
|
sort.SliceStable(
|
|
reservedList,
|
|
func(i, j int) (isLess bool) {
|
|
isLess = reservedList[i].String() < reservedList[j].String()
|
|
return
|
|
},
|
|
)
|
|
fmt.Println("The following reserved subnets were found to be conflicting with your allocation(s).")
|
|
for _, n := range reservedList {
|
|
resRec = reserved[*n]
|
|
fmt.Printf("\tNetwork: %s\n", n.String())
|
|
fmt.Printf("\t\tReservation: %s\n", resRec.Name)
|
|
fmt.Println("\t\tAllocations:")
|
|
for _, i := range resRec.Networks.Prefixes {
|
|
fmt.Printf("\t\t\t%s\n", i.String())
|
|
}
|
|
if verb >= 2 {
|
|
fmt.Println("\t\tReserved By:")
|
|
for _, i := range resRec.Spec.References {
|
|
fmt.Printf("\t\t\t%s %s\n", i.Type, i.Reference)
|
|
}
|
|
}
|
|
if verb >= 3 {
|
|
if resRec.Source != nil && resRec.Source.Evaluated != nil && resRec.Source.Applicable != nil && *resRec.Source.Applicable {
|
|
fmt.Printf("\t\tIs Source:\t%v\n", *resRec.Source.Evaluated)
|
|
}
|
|
if resRec.Dest != nil && resRec.Dest.Evaluated != nil && resRec.Dest.Applicable != nil && *resRec.Dest.Applicable {
|
|
fmt.Printf("\t\tIs Destination:\t%v\n", *resRec.Dest.Evaluated)
|
|
}
|
|
if resRec.Forwardable != nil && resRec.Forwardable.Evaluated != nil && resRec.Forwardable.Applicable != nil && *resRec.Forwardable.Applicable {
|
|
fmt.Printf("\t\tIs Forwardable:\t%v\n", *resRec.Forwardable.Evaluated)
|
|
}
|
|
if resRec.GlobalReach != nil && resRec.GlobalReach.Evaluated != nil && resRec.GlobalReach.Applicable != nil && *resRec.GlobalReach.Applicable {
|
|
fmt.Printf("\t\tIs Globally Reachable:\t%v\n", *resRec.GlobalReach.Evaluated)
|
|
}
|
|
if resRec.ProtoReserved != nil && resRec.ProtoReserved.Evaluated != nil && resRec.ProtoReserved.Applicable != nil && *resRec.ProtoReserved.Applicable {
|
|
fmt.Printf("\t\tIs Reserved by Protocol:\t%v\n", *resRec.ProtoReserved.Evaluated)
|
|
}
|
|
fmt.Printf("\t\tAllocated: %s\n", time.Time(resRec.Allocation).String())
|
|
if resRec.Updated != nil {
|
|
fmt.Printf("\t\tUpdated: %s\n", time.Time(*resRec.Updated).String())
|
|
}
|
|
if resRec.Termination != nil {
|
|
fmt.Printf("\t\tUpdated: %s\n", time.Time(*resRec.Termination).String())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.Separator, "\t", false, args.Plain))
|
|
}
|
|
}
|
|
if verb >= 1 {
|
|
fmt.Println(sectSep1)
|
|
}
|
|
|
|
// Remaining
|
|
if !args.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.Separator, "\t", true, args.Plain))
|
|
}
|
|
}
|
|
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.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 printReserved(nets []*netip.Prefix, remaining *netipx.IPSet, args *common) (err error) {
|
|
|
|
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
|
|
}
|