package main import ( "bytes" "fmt" "log" "net/netip" "strconv" "strings" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" ) const ( sectBordChar string = "#" sectBordWdth int = 2 sectSpcPad int = 1 sectSidePad int = sectBordWdth + sectSpcPad sectBothPad int = sectSidePad * 2 ) type ( IpInfo struct { // Desc string Index int // IP netip.Addr Is4 bool Is4In6 bool Is6 bool IsGlobalUnicast bool IsLinkLocalUnicast bool IsInterfaceLocalMulticast bool IsLinkLocalMulticast bool IsMulticast bool IsLoopback bool IsPrivate bool IsUnspecified bool IsValid bool } ) var ( datHdrsFunc []string = []string{ "-", ".Is4", ".Is4In6()", ".Is6()", ".IsGlobalUnicast()", ".IsLinkLocalUnicast()", ".IsInterfaceLocalMulticast()", ".IsLinkLocalMulticast()", ".IsMulticast()", ".IsLoopback()", ".IsPrivate()", ".IsUnspecified()", ".IsValid()", } datHdrsKey []string = []string{ "Address Index (See Fig. 3)", "IPv4", "4-in-6", "IPv6", "Global Unicast", "Link-Local Unicast", "Interface Local Multicast", "Link-Local Multicast", "Multicast", "Loopback", "Private/LAN", "\"Unspecified\" Address", "Valid Address", } datHdrsShort = []string{ "IDX", "4", "6(4)", "6", "GU", "LLU", "ILM", "LLM", "M", "LO", "P", "U", "V", } exampleAddrs [][2]string = [][2]string{ [2]string{"IPv4", "203.0.113.10"}, // RFC 5737 address (https://datatracker.ietf.org/doc/html/rfc5737) [2]string{"IPv4 Unspecified", "0.0.0.0"}, // Generally used to represent all of IPv4 address space [2]string{"IPv4 Global Unicast", "173.230.132.76"}, // r00t2.io [2]string{"IPv4 Multicast (All) (RFC 1112 § 4)", "224.0.0.1"}, // Should encompass all the multicast below. 224.0.0.0 is reserved. [2]string{"IPv4 Multicast (Reserved) (RFC 1112 § 4)", "224.0.0.0"}, // Reserved per RFC but it should still report multicast. [2]string{"IPv4 Multicast (Link-Local Multicast/Local Network Control Block) (RFC 5771 § 4)", "224.0.0.18"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-4 (VRRP multicast addr) [2]string{"IPv4 Multicast (Internetwork Control Block) (RFC 5771 § 5)", "224.0.1.68"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-5 (mdhcpdiscover, RFC 2730) [2]string{"IPv4 Multicast (AD-HOC I) (RFC 5771 § 6)", "224.0.2.10"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-6 [2]string{"IPv4 Multicast (SDP/SAP) (RFC 5771 § 7)", "224.2.0.10"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-7 [2]string{"IPv4 Multicast (AD-HOC II) (RFC 5771 § 6)", "224.3.0.10"}, // (above) [2]string{"IPv4 Multicast (Source-Specific) (RFC 5771 § 8)", "232.0.0.10"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-8 [2]string{"IPv4 Multicast (GLOP) (RFC 5771 § 9)", "233.0.0.10"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-9 [2]string{"IPv4 Multicast (AD-HOC III) (RFC 5771 § 6)", "233.252.0.10"}, // (above) [2]string{"IPv4 Multicast (Administrative) (RFC 5771 § 10)", "239.0.0.10"}, // https://datatracker.ietf.org/doc/html/rfc5771#section-10 [2]string{"IPv4 Link-Local Unicast (RFC 3927 § 2.1)", "169.254.1.10"}, // https://datatracker.ietf.org/doc/html/rfc3927#section-2.1 [2]string{"IPv4 Loopback", "127.0.1.10"}, // It's actually 127/8. Cannot believe how many people do not know this. [2]string{"IPv4 Private (RFC 1918)", "10.0.0.10"}, // https://datatracker.ietf.org/doc/html/rfc1918 [2]string{"4-in-6 (RFC 4291 § 2.5.5.1)", "::203.0.113.10"}, // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [2]string{"4-in-6 (RFC 4291 § 2.5.5.1) (Native)", "::cb00:710a"}, // "" [2]string{"4-in-6 (RFC 4291 § 2.5.5.2)", "::ffff:203.0.113.10"}, // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 [2]string{"4-in-6 (RFC 4291 § 2.5.5.2) (Native)", "::ffff:cb00:710a"}, // "" [2]string{"IPv6", "2001:db8::cb00:710a"}, // RFC 3849 (https://datatracker.ietf.org/doc/html/rfc3849) / RFC 9637 (https://datatracker.ietf.org/doc/html/rfc9637) address [2]string{"IPv6 Unspecified", "::"}, // Generally used to represent all of IPv6 address space [2]string{"IPv6 Global Unicast", "2600:3c02::f03c:91ff:fe93:c0a7"}, // r00t2.io [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (Reserved Net)", "ff00::"}, // https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1 [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (Reserved)", "ff00::1"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Nodes) (Interface-Local)", "ff01::1"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Nodes) (Link-Local)", "ff02::1"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Interface-Local)", "ff01::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Link-Local)", "ff02::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Admin-Local)", "ff04::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Site-Local)", "ff05::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Org-Local)", "ff08::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (All Routers) (Internet/Global)", "ff0e::2"}, // "" [2]string{"IPv6 Multicast (RFC 4291 § 2.7.1) (Solicited Node)", "ff02::1:ff00:10"}, // "" [2]string{"IPv6 Link-Local Unicast (RFC 4291 § 2.5.6)", "fe80::cb00:710a"}, // https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.6 [2]string{"IPv6 Loopback", "::1"}, // It's explicitly always a /128 with the address ::1 per RFC 4291 § 2.5.3. [2]string{"IPv6 Private (Unique-Local Addresses) (RFC 4193) (Reserved)", "fc00::10"}, // https://datatracker.ietf.org/doc/html/rfc4193 [2]string{"IPv6 Private (Unique-Local Addresses) (RFC 4193) (Valid)", "fd00::10"}, // "" } descs []string = make([]string, len(exampleAddrs)) ips []netip.Addr = make([]netip.Addr, len(exampleAddrs)) sectSep string = "\n" + strings.Repeat("-", 80) + "\n" ) func genHdr(sectNm string) (s string) { // Wish I finished stringsx.Banner at time of writing this... var fillLen int = sectBothPad + len(sectNm) s = fmt.Sprintf( "%s\n"+ // top border // begin title line "%-*s"+ // left-justify/right-pad "%s"+ // sectNm text "%[2]*[3]s\n"+ // one-indexed; repeat pad num and pad str from left border as right border // end title line "%[1]s\n", // bottom border strings.Repeat(sectBordChar, fillLen), // top and bottom (bottom uses index) sectSidePad, strings.Repeat(sectBordChar, sectBordWdth), // title left and right borders (right uses index) sectNm, // title text ) return } func main() { var err error var idx int var s string var desc string var pair [2]string var buf *bytes.Buffer = new(bytes.Buffer) var tbl *tablewriter.Table = tablewriter.NewTable( buf, tablewriter.WithConfig( tablewriter.Config{ Header: tw.CellConfig{ Formatting: tw.CellFormatting{ AutoFormat: tw.Off, }, }, }, ), // requires .Batch(), and the autoheader forces all caps. // https://github.com/olekukonko/tablewriter/issues/143 // https://github.com/olekukonko/tablewriter/issues/190 /* tablewriter.WithBehavior( tw.Behavior{ Structs: tw.Struct{ AutoHeader: tw.On, }, }, ), */ tablewriter.WithRendition( tw.Rendition{ Settings: tw.Settings{ Separators: tw.Separators{BetweenRows: tw.On}, }, }, ), ) defer func() { var tErr error if tbl != nil { if tErr = tbl.Close(); tErr != nil { log.Printf("Error closing table: %v", tErr) } } }() buf.WriteString(genHdr("Fig. 1: Address Evaluations")) buf.WriteString("(See Fig. 2 for a key of header symbols to names)\n\n") tbl.Header(datHdrsShort) for idx, pair = range exampleAddrs { desc, s = pair[0], pair[1] descs[idx] = desc if ips[idx], err = netip.ParseAddr(s); err != nil { log.Panicln(err) } // Currently no way to skip cols etc. https://github.com/olekukonko/tablewriter/issues/317 // rows[idx] = IpInfo{ if err = tbl.Append( IpInfo{ // Desc: desc, Index: idx, // IP: ip, Is4: ips[idx].Is4(), Is4In6: ips[idx].Is4In6(), Is6: ips[idx].Is6(), IsGlobalUnicast: ips[idx].IsGlobalUnicast(), IsLinkLocalUnicast: ips[idx].IsLinkLocalUnicast(), IsInterfaceLocalMulticast: ips[idx].IsInterfaceLocalMulticast(), IsLinkLocalMulticast: ips[idx].IsLinkLocalMulticast(), IsMulticast: ips[idx].IsMulticast(), IsLoopback: ips[idx].IsLoopback(), IsPrivate: ips[idx].IsPrivate(), IsUnspecified: ips[idx].IsUnspecified(), IsValid: ips[idx].IsValid(), }, ); err != nil { log.Panicln(err) } } /* if err = tbl.Bulk(rows); err != nil { log.Panicln(err) } */ if err = tbl.Render(); err != nil { log.Panicln(err) } buf.WriteString(sectSep) buf.WriteString(genHdr("Fig. 2: Headers Key for Fig. 1")) tbl.Reset() tbl.Header("Symbol", "Description", "net/netip.Addr Method") for idx = range datHdrsKey { if err = tbl.Append( []string{ datHdrsShort[idx], datHdrsKey[idx], datHdrsFunc[idx], }, ); err != nil { log.Panicln(err) } } if err = tbl.Render(); err != nil { log.Panicln(err) } buf.WriteString(sectSep) buf.WriteString(genHdr("Fig. 3: Test/Example IP Address Reference/Lookup for Fig. 1")) buf.WriteString("(See Fig. 4 for Descriptions/Detailed Information)\n\n") tbl.Reset() tbl.Header("Index", "Address (Raw)", "Address (Parsed)") for idx = range exampleAddrs { if err = tbl.Append( []string{ strconv.Itoa(idx), exampleAddrs[idx][1], ips[idx].String(), }, ); err != nil { log.Panicln(err) } } if err = tbl.Render(); err != nil { log.Panicln(err) } buf.WriteString(sectSep) buf.WriteString(genHdr("Fig. 4: Extended Information for Fig. 3")) tbl.Reset() tbl.Header("Index", "Description") for idx = range exampleAddrs { if err = tbl.Append( []string{ strconv.Itoa(idx), descs[idx], }, ); err != nil { log.Panicln(err) } } if err = tbl.Render(); err != nil { log.Panicln(err) } fmt.Println(buf.String()) }