server: canary DNS — authoritative zone with frozen §6.1 reference records
Stdlib DNS responder (no external deps): parses single-question queries
with EDNS OPT (bufsize, DO, ECS), serves the spec's frozen reference
records (ttl-{5,60,3600,86400} A/AAAA/TXT, many-rr 8×A in order, big-txt
~1800B), and per-query <nonce>.<session>.<zone> answers in 192.0.2.0/24.
UDP truncation sets TC past 512 (or the EDNS bufsize); TCP never
truncates — the EDNS-bufsize / TCP-fallback test. Every query is logged
(qname, resolver, transport, EDNS, ECS, case) and surfaced per session
prefix in GET /v1/sessions/{id}/observations as dns_canary. Profile gains
canary_zone + the canary-dns capability when configured.
Wire format validated against an independent client (correct rcodes,
answer counts, TC behavior, full EDNS response); unit tests cover
references, truncation-vs-EDNS, logging, NXDOMAIN.
Versioning: patch-first convention recorded in CLAUDE.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4f5499198b
commit
35baf70cdb
@@ -25,6 +25,7 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"echo-lot.app/server/internal/canarydns"
|
||||
"echo-lot.app/server/internal/config"
|
||||
"echo-lot.app/server/internal/control"
|
||||
"echo-lot.app/server/internal/dataplane"
|
||||
@@ -204,10 +206,40 @@ func serve(cfg *config.Config) error {
|
||||
ctl.Capabilities = caps
|
||||
}
|
||||
|
||||
// Canary DNS (spec §6.1) — authoritative for CanaryZone, udp+tcp per addr.
|
||||
var dnsUDP []*net.UDPConn
|
||||
var dnsTCP []net.Listener
|
||||
if dnsAddrs := config.Addrs(cfg.DNSListen); len(dnsAddrs) > 0 && cfg.CanaryZone != "" {
|
||||
v4, v6 := firstByFamily(dnsAddrs)
|
||||
cd := canarydns.New(cfg.CanaryZone, cfg.Name, v4, v6)
|
||||
for _, addr := range dnsAddrs {
|
||||
ua, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns udp addr %s: %w", addr, err)
|
||||
}
|
||||
uc, err := net.ListenUDP("udp", ua)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns udp listen %s: %w", addr, err)
|
||||
}
|
||||
dnsUDP = append(dnsUDP, uc)
|
||||
go func(a string, c *net.UDPConn) { errCh <- fmt.Errorf("dns-udp %s: %w", a, cd.ServeUDP(c)) }(addr, uc)
|
||||
|
||||
tl, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dns tcp listen %s: %w", addr, err)
|
||||
}
|
||||
dnsTCP = append(dnsTCP, tl)
|
||||
go func(a string, l net.Listener) { errCh <- fmt.Errorf("dns-tcp %s: %w", a, cd.ServeTCP(l)) }(addr, tl)
|
||||
}
|
||||
ctl.CanaryZone = cfg.CanaryZone
|
||||
ctl.CanaryQueries = func(prefix string) any { return cd.RecentForPrefix(prefix) }
|
||||
ctl.Capabilities = append(ctl.Capabilities, "canary-dns")
|
||||
}
|
||||
|
||||
slog.Info("listening",
|
||||
"control", ctlAddrs, "admin", cfg.AdminListen, "udp", udpAddrs,
|
||||
"tcp", config.Addrs(cfg.TCPListen), "stun", config.Addrs(cfg.StunListen),
|
||||
"capabilities", ctl.Capabilities)
|
||||
"dns", config.Addrs(cfg.DNSListen), "capabilities", ctl.Capabilities)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -225,12 +257,33 @@ func serve(cfg *config.Config) error {
|
||||
if stunSrv != nil {
|
||||
stunSrv.Close()
|
||||
}
|
||||
for _, c := range dnsUDP {
|
||||
_ = c.Close()
|
||||
}
|
||||
for _, l := range dnsTCP {
|
||||
_ = l.Close()
|
||||
}
|
||||
return nil
|
||||
case err := <-errCh:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// firstByFamily returns the first v4 and first v6 address from a list of
|
||||
// "ip:port" specs — used for the canary zone's apex/NS answers.
|
||||
func firstByFamily(addrs []string) (v4, v6 netip.Addr) {
|
||||
for _, a := range addrs {
|
||||
if ap, err := netip.ParseAddrPort(a); err == nil {
|
||||
if ap.Addr().Unmap().Is4() && !v4.IsValid() {
|
||||
v4 = ap.Addr().Unmap()
|
||||
} else if ap.Addr().Is6() && !ap.Addr().Is4In6() && !v6.IsValid() {
|
||||
v6 = ap.Addr()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func firstAddr(spec string) string {
|
||||
if a := config.Addrs(spec); len(a) > 0 {
|
||||
return a[0]
|
||||
|
||||
Reference in New Issue
Block a user