diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..53aad5e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +**Numa** — a portable DNS resolver with ad blocking, developer overrides, and a live dashboard. Built from scratch in Rust. Named after Numa Pompilius, the Roman king who established lasting institutions. + +Today: DNS forwarding/caching proxy with ad blocking, ephemeral overrides, live dashboard, and system DNS integration. +Next: Self-sovereign DNS via pkarr/Mainline DHT. +Vision: Incentivized resolver network with staking, challenge-based auditing, and token economics. + +## Build & Run + +```bash +cargo build # compile +sudo cargo run # run with default config (numa.toml) +sudo cargo run -- path/to/config # run with custom config path +RUST_LOG=debug sudo cargo run # verbose logging +make lint # clippy + rustfmt check +``` + +Test with: `dig @127.0.0.1 google.com` + +CLI commands: +```bash +numa help # show all commands +numa install # set system DNS to 127.0.0.1 +numa uninstall # restore original DNS +numa service start # install as persistent service (launchd/systemd) +numa service stop # uninstall service + restore DNS +numa service status # check service status +``` + +Dashboard: http://localhost:5380 + +## Architecture + +``` +UDP :53 ──▶ handle_query() + │ + ├─ 1. Override Store (ephemeral, auto-expiry) + ├─ 2. Blocklist (385K+ domains, subdomain matching) + ├─ 3. Local Zones (TOML config) + ├─ 4. Cache (TTL-aware, lazy eviction) + └─ 5. Upstream Forward (auto-detected from OS, conditional forwarding) + +HTTP :5380 ──▶ Axum REST API (19 endpoints) + Dashboard +``` + +### Source Files + +``` +src/ + main.rs # startup: load config, bind UDP, spawn API, blocklist download, per-query task loop + lib.rs # module declarations, Error/Result type aliases + ctx.rs # ServerCtx shared state + handle_query() pipeline + api.rs # Axum REST server (19 endpoints, port 5380) + embedded dashboard + config.rs # TOML config loading with defaults (server, upstream, cache, blocking, zones) + blocklist.rs # BlocklistStore — HashSet, download, parse, subdomain matching, check + override_store.rs # OverrideStore — ephemeral domain overrides with auto-expiry + query_log.rs # ring buffer (VecDeque, 1000 entries) for recent queries + cache.rs # DnsCache — TTL-aware, lazy eviction every 1000 lookups + forward.rs # async UDP forwarding to upstream resolver + stats.rs # ServerStats counters + QueryPath enum (6 categories) + system_dns.rs # OS DNS discovery (scutil/resolv.conf), install/uninstall, service management + buffer.rs # BytePacketBuffer — 4096-byte DNS wire format I/O + header.rs # DnsHeader — 12-byte bitfield parsing/serialization + question.rs # DnsQuestion + QueryType enum (A, NS, CNAME, MX, AAAA) + record.rs # DnsRecord enum — wire format read/write per record type (filters UNKNOWN on write) + packet.rs # DnsPacket — header + questions + answers + authorities + resources +site/ + dashboard.html # live dashboard (embedded at compile time via include_str!) + index.html # landing page (Roman Stone theme) +``` + +## Config + +`numa.toml` at project root. Sections: `[server]`, `[upstream]`, `[cache]`, `[blocking]`, `[[zones]]`. Falls back to sensible defaults if file is missing. Upstream auto-detected from system resolver if not set. + +## REST API + +Dashboard: GET `/` (embedded HTML) +Override management: POST/GET/DELETE `/overrides`, POST `/overrides/environment` +Blocking: GET `/blocking/stats`, PUT `/blocking/toggle`, POST `/blocking/pause`, GET/POST `/blocking/allowlist`, GET `/blocking/check/{domain}` +Diagnostics: GET `/diagnose/{domain}`, `/query-log`, `/stats`, `/cache`, `/health` +Cache: DELETE `/cache`, `/cache/{domain}` + +## Key Details + +- Rust 2021 edition, async via `tokio` (rt-multi-thread) +- Deps: tokio, axum, serde, serde_json, toml, log, env_logger, reqwest (zero DNS libraries) +- DNS buffer size: 4096 bytes (EDNS-compatible). UNKNOWN record types (e.g. OPT) filtered on serialization. +- `BytePacketBuffer::read_qname` handles label compression (pointer jumps) +- `type Error = Box` / `type Result` aliased in `lib.rs` +- Shared state via `Arc` with `std::sync::Mutex` (sub-microsecond holds, never across `.await`) +- Cache: TTL clamped between `min_ttl` and `max_ttl`, lazy eviction every 1000 queries +- Blocklist: parsed outside lock, swapped atomically. `is_blocked()` takes `&self` (read-only). +- Upstream: auto-detected from `scutil --dns` (macOS) or `/etc/resolv.conf` (Linux). Falls back to Quad9. +- Conditional forwarding: Tailscale/VPN domains auto-routed to correct upstream. +- macOS service: launchd plist with KeepAlive + RunAtLoad. Use `launchctl bootstrap/bootout` (not load/unload). +- Logging controlled via `RUST_LOG` env var. Default: `info`