check / check (push) Canceled after 0s
The home page gets cards for the newest and oldest kept route, the newest and oldest updated AS, the AS with the most prefixes, the ASes with a single prefix and fifteen well-known networks. Two more cards count the bogon announcements and the prefixes with several origin ASes. They link to the new pages /bogons and /multiple-origins, which list up to 1,000 entries each. The new package internal/bogon tells reserved prefixes and AS numbers, from the IANA special-purpose and address space registries. Table.Overview walks the table and lets the read lock go every 10,000 prefixes. The server works the cards out once a minute and serves that copy. A new index on asns(last_seen) finds the newest and oldest updated AS. Model: opus-5-5
203 lines
8.0 KiB
Go
203 lines
8.0 KiB
Go
// 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"},
|
|
}
|