diff --git a/README.adoc b/README.adoc index 1cd1686..24a8def 100644 --- a/README.adoc +++ b/README.adoc @@ -50,7 +50,7 @@ A tool to assist in design of segregate/segment/split/subnet networks. **** If the `XDG_CACHE_HOME` environment variable is not present... ***** On macOS, an explicit fallback of `~/Library/Caches/subnetter/` will be used. (To my knowledge/understanding, this is the standard user cache directory and cannot be changed.) This usually evaluates to `/Users//Library/Caches/subnetter/`. ***** On all others, an explicit fallback of `~/.cache/subnetter` will be used. -****** On most non-macOS \*NIX-like systems , this is usually `/home//.cache/subetter/`, provided normal user homes. On http://p9f.org/[Plan9^] platforms (e.g. https://9p.io/plan9/index.html[Plan 9 4th Ed.^], https://9front.org/[9front^], http://9legacy.org/[9legacy^]), the `/env/home` environment variable (`$home`) will be used, the `./lib/` subdirectory under there (which typically/should already exist) will be appended to it, and that appended with `./cache/subnetter/` (this usually evaluates to `/usr//lib/cache/subnetter/`). +****** On most non-macOS/*NIX-like systems , this is usually `/home//.cache/subetter/`, provided normal user homes. On http://p9f.org/[Plan9^] platforms (e.g. https://9p.io/plan9/index.html[Plan 9 4th Ed.^], https://9front.org/[9front^], http://9legacy.org/[9legacy^]), the `/env/home` environment variable (`$home`) will be used, the `./lib/` subdirectory under there (which typically/should already exist) will be appended to it, and that appended with `./cache/subnetter/` (this usually evaluates to `/usr//lib/cache/subnetter/`). *** For Windows systems... **** If https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#constants[the `LOCALAPPDATA` environment variable^] is present, it will be `%LOCALAPPDATA%\Cache\subnetter\` (or `${env:LOCALAPPDATA}\Cache\subnetter\` in Powershell syntax). This usually evaluates to `C:\Users\\AppData\Local\Cache\subnetter\`. @@ -61,3 +61,5 @@ This program in general draws inspiration from `ipcalc` (http://jodies.de/ipcalc The `table` subcommand is inspired by `iptab` from https://metacpan.org/pod/Net::IP[Perl Net-IP^]. Additional notes for certain contexts are primarily taken from https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing[the Wikipedia article on _Classless Inter-Domain Routing_^] (as of _Jan 28, 2025_). + +Reservations are pulled/cached directly from the IANA registries (https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml[IPv4^], https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml[IPv6^]). \ No newline at end of file diff --git a/TODO b/TODO index a6bade1..a7f5968 100644 --- a/TODO +++ b/TODO @@ -1 +1,5 @@ -- add table rendering for reserved networks? \ No newline at end of file +- add table rendering for reserved networks? + +- when checking/rendering reserved networks, currently the footnotes aren't returned. +-- netsplit.IANARegistryFootnote +-- encapsulated in the IANARegistry.Footnotes \ No newline at end of file diff --git a/cmd/subnetter/errs.go b/cmd/subnetter/errs.go index dd067b9..0742973 100644 --- a/cmd/subnetter/errs.go +++ b/cmd/subnetter/errs.go @@ -5,5 +5,6 @@ import ( ) var ( + ErrBadFmt error = errors.New("unknown output format") errBadNet error = errors.New("bad inet/addr family/version") ) diff --git a/cmd/subnetter/funcs.go b/cmd/subnetter/funcs.go index ac00713..96d7809 100644 --- a/cmd/subnetter/funcs.go +++ b/cmd/subnetter/funcs.go @@ -471,7 +471,180 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem return } -func printReserved(nets []*netip.Prefix, remaining *netipx.IPSet, args *common) (err error) { +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 } diff --git a/cmd/subnetter/main.go b/cmd/subnetter/main.go index ac6c087..82f83fe 100644 --- a/cmd/subnetter/main.go +++ b/cmd/subnetter/main.go @@ -35,6 +35,7 @@ func main() { var res *netsplit.StructuredResults var noStrict bool var strictErr error + var reservations map[netip.Prefix]*netsplit.IANAAddrNetResRecord var splitErr *netsplit.SplitErr = new(netsplit.SplitErr) var parser *flags.Parser = flags.NewParser(args, flags.Default) @@ -79,7 +80,25 @@ func main() { } return case "reserved": - // TODO + if origPfx, err = netip.ParsePrefix(args.Check.Network.Network); err != nil { + log.Panicln(err) + } + nets = make([]*netip.Prefix, 1) + nets[0] = new(netip.Prefix) + *nets[0] = origPfx + if err = netsplit.SetCachePath(args.Check.CacheDir); err != nil { + log.Panicln(err) + } + if err = netsplit.EnableCache(args.Check.DoResCache); err != nil { + log.Panicln(err) + } + if reservations, err = netsplit.CheckReserved(nets, !args.Check.NoRevRecursive, !args.Check.NoRecursive, !args.Check.NoPrivate); err != nil { + log.Panicln(err) + } + if err = printReserved(reservations, origPfx, args.Check.Plain, args.Check.Fmt); err != nil { + log.Panicln(err) + } + return case "table": // Account for a weird redundant CLI condition. if args.Table.NoIpv4 && args.Table.NoIpv6 { diff --git a/netsplit/funcs_cache.go b/netsplit/funcs_cache.go index e75d912..6f29b36 100644 --- a/netsplit/funcs_cache.go +++ b/netsplit/funcs_cache.go @@ -195,6 +195,7 @@ func SetCachePath(cacheDirPath string) (err error) { } if cacheDirPath != oldPath { + cacheDir = cacheDirPath if err = os.MkdirAll(cacheDir, cacheDirPerms); err != nil { return } diff --git a/netsplit/funcs_cidrsplitter.go b/netsplit/funcs_cidrsplitter.go index 19d8aca..31cb047 100644 --- a/netsplit/funcs_cidrsplitter.go +++ b/netsplit/funcs_cidrsplitter.go @@ -31,7 +31,7 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e return } - if c.PrefixLength > uint8(base.Bits()) { + if c.PrefixLength < uint8(base.Bits()) { err = ErrBigPrefix return } @@ -48,10 +48,10 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e // We just hit the end of the prefix. break } - subPtr = new(netip.Prefix) - *subPtr = sub - nets = append(nets, subPtr) } + subPtr = new(netip.Prefix) + *subPtr = sub + nets = append(nets, subPtr) } return