- degradeUpstream now takes the caller's snapshot length: a concurrent
refresh that grows the live upstream list no longer lets the retry
index past the stale snapshot and panic the DNS handler.
- Internal reverse lookups take a dedicated forward path that never runs
the degrade state machine: the query goes only to the selected internal
upstream and surfaces SERVFAIL on failure instead of retrying onto a
public fallback, closing the TOCTOU leak in the PTR gate.
- Local DNS replies set the RecursionAvailable bit per RFC 1035.
- A failed TCP retry for a truncated UDP response keeps the truncated
response (with TC set) instead of dropping it for SERVFAIL, so
networks that block TCP/53 still get usable answers.
Monitoring overhaul for the admin console:
- Add internal/metrics: OpenTelemetry meter with Prometheus exporter
(counters for bytes/dns/connections/rule hits, updown counter for
active connections, histogram for connection duration, process
gauges). Served unauthenticated at GET /metrics; an OTLP exporter
can be added later without touching call sites.
- Fix uptime: TrafficSnapshot.Uptime was a time.Duration serialized
as nanoseconds while the frontend renders seconds; it is now int64
seconds. Stats is created before rule loading so uptime tracks the
process start.
- Extend admin.Stats: per-protocol active connections (idempotent
decrement on Close), 60s sliding-window rates, rule-hit counters,
process resources, and a bounded in-process history ring (1h at 5s
sampling, gap-clamped) served at GET /api/history.
- Router route observer: block/direct decisions in DialSmart, proxy
decisions in DialProxyOnly (covers HTTP/HTTPS paths), exactly once
per connection.
- Fix socks5 handler closing the raw conn instead of the wrapped one,
which would have leaked active-connection counts.
- Frontend: Overview gains rate cards, active connections, rule hits,
system stats, and four hand-rolled SVG area charts (traffic, DNS/
conns, active per protocol, rule hits) with hover readouts.
RuleSet retains the raw rule list alongside the suffix tree so rules can be
listed, added and removed at runtime. Removal rebuilds the tree from the
retained list; startup batch loading keeps the old single-compaction flow.
Router.BlockRule/DirectRule/ProxyRule migrate to *RuleSet.