Resolve PTR names over DNS-over-HTTPS and cache them for the record TTL (closes #238)
check / check (push) Successful in 4m39s

`ptr` keeps its field and shape. It now comes from Google, Cloudflare, AdGuard and Quad9 over RFC 8484, tried in turn within 2 seconds, in place of the host's resolver with no cache. Answers stay in memory and in a new `cache.db` for the record's TTL, at most 250,000 addresses. A reserved address is not sent to any resolver and has an empty `ptr`.

Judgement call: each resolver gets an equal share of the time left, so a hung one does not use up the limit.
Judgement call: the negative TTL is the smaller of the SOA minimum and the SOA's own TTL.
Deviation: the cache is SQLite, by sneak's words, not the JSON file the standing rule asks for.

Model: opus-5-5
This commit was merged in pull request #244.
This commit is contained in:
clawbot committed 2026-11-10 13:48:06 +01:00
1 parent 641798bb22
commit 0cbd9cd943
21 files changed
+1973 -69

No files matched your search

+11 -2
View File
@@ -1,5 +1,5 @@
// 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
// 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.
//
@@ -42,6 +42,15 @@ func Prefix(prefix netip.Prefix) (string, bool) {
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) {
+38 -2
View File
@@ -8,6 +8,9 @@ import (
"git.eeqj.de/sneak/routewatch/internal/bogon"
)
// privateUseTen is why a prefix or an address inside 10.0.0.0/8 is reserved.
const privateUseTen = "Private-Use (10.0.0.0/8)"
func TestPrefix(t *testing.T) {
t.Parallel()
@@ -16,8 +19,8 @@ func TestPrefix(t *testing.T) {
// want is why the prefix is reserved, or "" when it is not.
want string
}{
{"10.1.2.0/24", "Private-Use (10.0.0.0/8)"},
{"10.0.0.0/8", "Private-Use (10.0.0.0/8)"},
{"10.1.2.0/24", privateUseTen},
{"10.0.0.0/8", privateUseTen},
{"192.0.2.0/24", "Documentation (TEST-NET-1) (192.0.2.0/24)"},
{"2001:db8:1::/48", "Documentation (2001:db8::/32)"},
{"fd00::/8", "Unique-Local (fc00::/7)"},
@@ -56,6 +59,39 @@ func TestPrefix(t *testing.T) {
}
}
func TestAddr(t *testing.T) {
t.Parallel()
tests := []struct {
addr string
// want is why the address is reserved, or "" when it is not.
want string
}{
{"10.0.0.1", privateUseTen},
{"fd00::1", "Unique-Local (fc00::/7)"},
// An IPv4 address written in IPv6 form counts as the IPv4 address.
{"::ffff:10.0.0.1", privateUseTen},
{"::ffff:8.8.8.8", ""},
// An address that may be announced, inside a reserved block.
{"192.0.0.9", ""},
// Addresses anyone may announce.
{"8.8.8.8", ""},
{"2001:4860:4860::8888", ""},
}
for _, tc := range tests {
t.Run(tc.addr, func(t *testing.T) {
t.Parallel()
reason, reserved := bogon.Addr(netip.MustParseAddr(tc.addr))
if reason != tc.want || reserved != (tc.want != "") {
t.Errorf("Addr(%s) = %q, %v; want %q", tc.addr, reason,
reserved, tc.want)
}
})
}
}
func TestASN(t *testing.T) {
t.Parallel()