// Package bogon tells whether a prefix or an AS number is reserved: set aside // for a special purpose, so that no network should announce it on the // internet. A route is a bogon announcement when its prefix is reserved or its // origin AS is. // // Most reserved prefixes come from the IANA special-purpose address registries // for IPv4 and IPv6. The two multicast blocks, 224.0.0.0/4 and ff00::/8, come // from the IANA IPv4 and IPv6 address space registries. The reserved AS // numbers come from the IANA special-purpose AS numbers registry: // - the AS numbers for private use // - the AS numbers for documentation // - AS 23456, which stands in for a 32-bit AS number // - the last AS number of the 16-bit and of the 32-bit range // // Address space and AS numbers that the IANA has not handed out yet are not // counted. package bogon import ( "net/netip" "strconv" ) // Prefix returns why prefix is reserved, and true, when it lies inside a // reserved block. The most specific registry block that holds prefix decides, // because some blocks that may be announced lie inside reserved ones. It // returns "" and false when prefix is not reserved. func Prefix(prefix netip.Prefix) (string, bool) { var holder *block for i, b := range registryBlocks { inside := b.prefix.Bits() <= prefix.Bits() && b.prefix.Contains(prefix.Addr()) if inside && (holder == nil || b.prefix.Bits() > holder.prefix.Bits()) { holder = ®istryBlocks[i] } } if holder == nil || !holder.reserved { return "", false } return holder.name + " (" + holder.prefix.String() + ")", true } // ASN returns why asn is reserved, and true, when it is. It returns "" and // false when asn is not reserved. func ASN(asn int) (string, bool) { for _, r := range reservedASRanges { if asn < r.first || asn > r.last { continue } if r.first == r.last { return r.name + " (AS" + strconv.Itoa(asn) + ")", true } return r.name + " (AS" + strconv.Itoa(r.first) + "-AS" + strconv.Itoa(r.last) + ")", true } return "", false } // The names that the registries give to more than one row. const ( privateUse = "Private-Use" documentation = "Documentation" benchmarking = "Benchmarking" ietfProtocolAssignments = "IETF Protocol Assignments" portControlAnycast = "Port Control Protocol Anycast" natTraversalAnycast = "Traversal Using Relays around NAT Anycast" nat64Discovery = "NAT64/DNS64 Discovery" amt = "AMT" as112DirectDelegation = "Direct Delegation AS112 Service" ipv4IPv6Translation = "IPv4-IPv6 Translat." multicast = "Multicast" ) // block is one row of an IANA address registry. type block struct { prefix netip.Prefix name string // reserved says that no network should announce the row's addresses on // the internet. reserved bool } // reservedBlock returns a reserved row: a multicast block, or a special-purpose // row whose Globally Reachable column is False. func reservedBlock(prefix, name string) block { return block{prefix: netip.MustParsePrefix(prefix), name: name, reserved: true} } // reachableBlock returns a special-purpose row whose Globally Reachable column // is True or N/A. Such a row matters only inside a reserved block, whose // addresses it lets be announced. func reachableBlock(prefix, name string) block { return block{prefix: netip.MustParsePrefix(prefix), name: name} } // registryBlocks are the rows of the IANA special-purpose address registries, // without the deprecated ones: // - https://www.iana.org/assignments/iana-ipv4-special-registry/ // - https://www.iana.org/assignments/iana-ipv6-special-registry/ // // They also hold the multicast blocks, which the IANA address space registries // list as Multicast: // - https://www.iana.org/assignments/ipv4-address-space/ // - https://www.iana.org/assignments/ipv6-address-space/ // //nolint:gochecknoglobals // a fixed list, parsed once var registryBlocks = []block{ reservedBlock("0.0.0.0/8", "This network"), reservedBlock("0.0.0.0/32", "This host on this network"), reservedBlock("10.0.0.0/8", privateUse), reservedBlock("100.64.0.0/10", "Shared Address Space"), reservedBlock("127.0.0.0/8", "Loopback"), reservedBlock("169.254.0.0/16", "Link Local"), reservedBlock("172.16.0.0/12", privateUse), reservedBlock("192.0.0.0/24", ietfProtocolAssignments), reservedBlock("192.0.0.0/29", "IPv4 Service Continuity Prefix"), reservedBlock("192.0.0.8/32", "IPv4 dummy address"), reachableBlock("192.0.0.9/32", portControlAnycast), reachableBlock("192.0.0.10/32", natTraversalAnycast), reservedBlock("192.0.0.170/32", nat64Discovery), reservedBlock("192.0.0.171/32", nat64Discovery), reservedBlock("192.0.2.0/24", "Documentation (TEST-NET-1)"), reachableBlock("192.31.196.0/24", "AS112-v4"), reachableBlock("192.52.193.0/24", amt), reservedBlock("192.88.99.2/32", "6a44-relay anycast address"), reservedBlock("192.168.0.0/16", privateUse), reachableBlock("192.175.48.0/24", as112DirectDelegation), reservedBlock("198.18.0.0/15", benchmarking), reservedBlock("198.51.100.0/24", "Documentation (TEST-NET-2)"), reservedBlock("203.0.113.0/24", "Documentation (TEST-NET-3)"), reservedBlock("224.0.0.0/4", multicast), reservedBlock("240.0.0.0/4", "Reserved"), reservedBlock("255.255.255.255/32", "Limited Broadcast"), reservedBlock("::1/128", "Loopback Address"), reservedBlock("::/128", "Unspecified Address"), reservedBlock("::ffff:0:0/96", "IPv4-mapped Address"), reachableBlock("64:ff9b::/96", ipv4IPv6Translation), reservedBlock("64:ff9b:1::/48", ipv4IPv6Translation), reservedBlock("100::/64", "Discard-Only Address Block"), reservedBlock("100:0:0:1::/64", "Dummy IPv6 Prefix"), reservedBlock("2001::/23", ietfProtocolAssignments), reachableBlock("2001::/32", "TEREDO"), reachableBlock("2001:1::1/128", portControlAnycast), reachableBlock("2001:1::2/128", natTraversalAnycast), reachableBlock("2001:1::3/128", "DNS-SD Service Registration Protocol Anycast"), reservedBlock("2001:2::/48", benchmarking), reachableBlock("2001:3::/32", amt), reachableBlock("2001:4:112::/48", "AS112-v6"), reachableBlock("2001:20::/28", "ORCHIDv2"), reachableBlock("2001:30::/28", "Drone Remote ID Protocol Entity Tags (DETs) Prefix"), reservedBlock("2001:db8::/32", documentation), reachableBlock("2002::/16", "6to4"), reachableBlock("2620:4f:8000::/48", as112DirectDelegation), reservedBlock("3fff::/20", documentation), reservedBlock("5f00::/16", "Segment Routing (SRv6) SIDs"), reservedBlock("fc00::/7", "Unique-Local"), reservedBlock("fe80::/10", "Link-Local Unicast"), reservedBlock("ff00::/8", multicast), } // asRange is a range of reserved AS numbers, first to last. type asRange struct { first, last int name string } // The reserved AS numbers of the IANA special-purpose AS numbers registry. const ( asTrans = 23456 firstDocumentationAS16 = 64496 lastDocumentationAS16 = 64511 firstPrivateUseAS16 = 64512 lastPrivateUseAS16 = 65534 lastAS16 = 65535 firstDocumentationAS32 = 65536 lastDocumentationAS32 = 65551 firstPrivateUseAS32 = 4200000000 lastPrivateUseAS32 = 4294967294 lastAS32 = 4294967295 ) // reservedASRanges are the reserved rows of the IANA special-purpose AS numbers // registry, https://www.iana.org/assignments/iana-as-numbers-special-registry/. // AS 0 is left out: RouteWatch uses it for a route with no known origin. AS 112 // is left out because the AS112 project announces routes with it. // //nolint:gochecknoglobals // a fixed list var reservedASRanges = []asRange{ {first: asTrans, last: asTrans, name: "AS_TRANS"}, {first: firstDocumentationAS16, last: lastDocumentationAS16, name: documentation}, {first: firstPrivateUseAS16, last: lastPrivateUseAS16, name: privateUse}, {first: lastAS16, last: lastAS16, name: "Last 16-bit AS number"}, {first: firstDocumentationAS32, last: lastDocumentationAS32, name: documentation}, {first: firstPrivateUseAS32, last: lastPrivateUseAS32, name: privateUse}, {first: lastAS32, last: lastAS32, name: "Last 32-bit AS number"}, }