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{ commonBase: commonBase{ 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, ``+ "\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(records map[netip.Prefix]*netsplit.IANAAddrNetResRecord, origNet netip.Prefix, plain bool, fmtType string) (err error) { var b []byte var idx int var pfx netip.Prefix var rec *netsplit.IANAAddrNetResRecord var sortedKeys []netip.Prefix var sb = new(strings.Builder) switch fmtType { case "json": if b, err = json.MarshalIndent(records, "", " "); err != nil { return } fmt.Println(string(b)) return case "xml": if b, err = xml.MarshalIndent(records, "", " "); err != nil { return } fmt.Println(string(b)) return case "yml", "yaml": if b, err = yaml.Marshal(records); err != nil { return } fmt.Println(string(b)) return } if fmtType != "pretty" { err = ErrBadFmt return } if records == nil || len(records) == 0 { fmt.Println("No IANA/IETF/RFC-reserved subnet(s) found.") return } sortedKeys = make([]netip.Prefix, len(records)) idx = 0 for pfx, _ = range records { sortedKeys[idx] = pfx idx++ } sort.SliceStable( sortedKeys, func(i, j int) (isBefore bool) { isBefore = (netipx.ComparePrefix(sortedKeys[i], sortedKeys[j])) <= 0 return }, ) fmt.Fprintf(sb, "= %s =\n", origNet.String()) for _, pfx = range sortedKeys { rec = records[pfx] fmt.Fprint(sb, sectFmts[plain][0]+"\n") // Name fmt.Fprintf(sb, "Reservation Name:\t%s\n", rec.Name) fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Networks fmt.Fprint(sb, "\tCanonical Reserved Networks:") if rec.Networks != nil { fmt.Fprint(sb, "\n") for _, recPfx := range rec.Networks.Prefixes { fmt.Fprint(sb, "\t\t"+sectFmts[plain][2]+"\n") fmt.Fprintf(sb, "\t\t%s\n", recPfx.String()) // TODO: Print footnotes/refs! } } else { fmt.Fprint(sb, "\t(N/A)\n") } fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Reference/Specification fmt.Fprint(sb, "\tSpecification:") if rec.Spec != nil { fmt.Fprint(sb, "\n") for _, line := range strings.Split(rec.Spec.Text, "\n") { fmt.Fprint(sb, "\t\t"+line+"\n") } if rec.Spec.References != nil { fmt.Fprintf(sb, "\t\t%s\n", sectFmts[plain][2]) for rIdx, recref := range rec.Spec.References { if recref != nil { fmt.Fprintf(sb, "\t\t[%d] (%s) %s\n", rIdx, recref.Type, recref.Reference) } } } } else { fmt.Fprint(sb, "\t(None)\n") } fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Allocated (always present) fmt.Fprintf(sb, "\tAllocated:\t%s\n", time.Time(rec.Allocation).String()) // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Updated fmt.Fprint(sb, "\tUpdated:\t") if rec.Updated != nil { fmt.Fprintf(sb, "%s\n", time.Time(*rec.Updated).String()) } else { fmt.Fprint(sb, "(N/A)\n") } // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Termination fmt.Fprint(sb, "\tTerminated:\t") if rec.Termination != nil { fmt.Fprintf(sb, "%s\n", time.Time(*rec.Termination).String()) } else { fmt.Fprint(sb, "(N/A)\n") } fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Source fmt.Fprint(sb, "\tValid Source:\t\t\t") if rec.Source != nil { if rec.Source.Applicable != nil && !bool(*rec.Source.Applicable) { fmt.Fprint(sb, "(N/A)\n") } else { fmt.Fprintf(sb, "%v\n", bool(*rec.Source.Evaluated)) } } else { fmt.Fprint(sb, "(N/A)\n") } // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Destination fmt.Fprint(sb, "\tValid Destination:\t\t") if rec.Dest != nil { if rec.Dest.Applicable != nil && !bool(*rec.Dest.Applicable) { fmt.Fprint(sb, "(N/A)\n") } else { fmt.Fprintf(sb, "%v\n", bool(*rec.Dest.Evaluated)) } } else { fmt.Fprint(sb, "(N/A)\n") } // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Forwardable fmt.Fprint(sb, "\tForwardable:\t\t\t") if rec.Forwardable != nil { if rec.Forwardable.Applicable != nil && !bool(*rec.Forwardable.Applicable) { fmt.Fprint(sb, "(N/A)\n") } else { fmt.Fprintf(sb, "%v\n", bool(*rec.Forwardable.Evaluated)) } } else { fmt.Fprint(sb, "(N/A)\n") } // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Globally reachable fmt.Fprint(sb, "\tGlobally Routable/Reachable:\t") if rec.GlobalReach != nil { if rec.GlobalReach.Applicable != nil && !bool(*rec.GlobalReach.Applicable) { fmt.Fprint(sb, "(N/A)\n") } else { fmt.Fprintf(sb, "%v\n", bool(*rec.GlobalReach.Evaluated)) } } else { fmt.Fprint(sb, "(N/A)\n") } // fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n") // Reserved by Protocol fmt.Fprint(sb, "\tReserved by Protocol:\t\t") if rec.ProtoReserved != nil { if rec.ProtoReserved.Applicable != nil && !bool(*rec.ProtoReserved.Applicable) { fmt.Fprint(sb, "(N/A)\n") } else { fmt.Fprintf(sb, "%v\n", bool(*rec.ProtoReserved.Evaluated)) } } else { fmt.Fprint(sb, "(N/A)\n") } } fmt.Print(sb.String()) 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 }