// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later // Package canarydns serves the authoritative canary zone (spec §6.1). The // reference records below are FROZEN by the protocol spec — names, TTLs, and // RDATA are ground truth the client compares against, so they must never // change without a spec revision and a matching update in the app. All // addresses are documentation-range (RFC 5737 192.0.2.0/24, RFC 3849 // 2001:db8::/32). package canarydns import "net/netip" // refRecord is one frozen reference name (relative to the zone) with its // per-type answers. A zero value in a field means "no record of that type". type refRecord struct { label string ttl uint32 a []netip.Addr // A / AAAA answers (order preserved) txt []string // one string per TXT record } // referenceRecords are the spec §6.1 fixed records. The RDATA constants are // frozen HERE (the spec calls this file the source of truth) and mirrored in // the app. Order within many-rr is part of the test (order/stripping check). var referenceRecords = []refRecord{ {label: "ttl-5", ttl: 5, a: []netip.Addr{netip.MustParseAddr("192.0.2.5"), netip.MustParseAddr("2001:db8::5")}, txt: []string{"echolot-ref ttl=5"}}, {label: "ttl-60", ttl: 60, a: []netip.Addr{netip.MustParseAddr("192.0.2.60"), netip.MustParseAddr("2001:db8::60")}, txt: []string{"echolot-ref ttl=60"}}, {label: "ttl-3600", ttl: 3600, a: []netip.Addr{netip.MustParseAddr("192.0.2.36"), netip.MustParseAddr("2001:db8::3600")}, txt: []string{"echolot-ref ttl=3600"}}, {label: "ttl-86400", ttl: 86400, a: []netip.Addr{netip.MustParseAddr("192.0.2.86"), netip.MustParseAddr("2001:db8::8640")}, txt: []string{"echolot-ref ttl=86400"}}, // many-rr: exactly 8 A records in defined order. {label: "many-rr", ttl: 300, a: []netip.Addr{ netip.MustParseAddr("192.0.2.101"), netip.MustParseAddr("192.0.2.102"), netip.MustParseAddr("192.0.2.103"), netip.MustParseAddr("192.0.2.104"), netip.MustParseAddr("192.0.2.105"), netip.MustParseAddr("192.0.2.106"), netip.MustParseAddr("192.0.2.107"), netip.MustParseAddr("192.0.2.108"), }}, // big-txt: ~1800 bytes, exercises EDNS bufsize / TCP fallback. {label: "big-txt", ttl: 300, txt: bigTxt()}, } // bigTxt builds a deterministic ~1800-byte TXT payload as a sequence of // 255-byte character-strings (the DNS TXT chunk limit). The content is fixed // so the client can verify integrity, not just length. func bigTxt() []string { const total = 1800 const pattern = "echolot-big-txt-reference-0123456789abcdef-" buf := make([]byte, 0, total) for len(buf) < total { buf = append(buf, pattern...) } buf = buf[:total] var out []string for len(buf) > 0 { n := min(255, len(buf)) out = append(out, string(buf[:n])) buf = buf[n:] } return out } // findReference returns the reference record for a label, or nil. func findReference(label string) *refRecord { for i := range referenceRecords { if referenceRecords[i].label == label { return &referenceRecords[i] } } return nil }