check / check (push) Successful in 1m34s
`internal/ptr` asks Google, Cloudflare, AdGuard and Quad9 in turn, 2 seconds in all per address. It keeps answers in memory and writes them through to `cache.db`, opened by the new `internal/cachedb`. A reserved address is not sent to the resolvers. Judgement calls: - Each resolver gets an equal share of the time left, so a hung resolver does not use up the 2 seconds. - The negative TTL is the smaller of the SOA minimum and the SOA's own TTL (RFC 2308). - Names that are not host names are dropped, as `net.Resolver.LookupAddr` does. Model: opus-5-5
212 lines
8.3 KiB
Go
212 lines
8.3 KiB
Go
// Package bogon tells whether a prefix, an address 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
|
|
}
|
|
|
|
// Addr returns why addr is reserved, and true, when it lies inside a reserved
|
|
// block, as Prefix decides. An IPv4 address written in IPv6 form counts as the
|
|
// IPv4 address it holds. It returns "" and false when addr is not reserved.
|
|
func Addr(addr netip.Addr) (string, bool) {
|
|
addr = addr.Unmap()
|
|
|
|
return Prefix(netip.PrefixFrom(addr, addr.BitLen()))
|
|
}
|
|
|
|
// 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"},
|
|
}
|