Add ad blocking, live dashboard, and system DNS auto-discovery #2

Merged
dearsky merged 0 commits from refs/pull/2/head into main 2026-03-20 17:53:00 +08:00
dearsky commented 2026-03-20 16:54:58 +08:00 (Migrated from gitea.proxy.dearsky.top)

What is Numa?

DNS you own. Everywhere you go.

A portable DNS resolver with ad blocking, developer overrides, and a live dashboard — in a single binary. No Raspberry Pi, no cloud account, no data leaving your machine.

Quick Start

cargo build
sudo cargo run            # binds to port 53, downloads blocklists on first run

Open http://localhost:5380 for the live dashboard.

dig @127.0.0.1 google.com        # ✓ resolves normally
dig @127.0.0.1 ads.google.com    # ✗ blocked → 0.0.0.0

Set Numa as your system DNS:

sudo numa install                 # saves current DNS, sets to 127.0.0.1
sudo numa uninstall               # restores original settings

What's New

Ad Blocking

  • 385K+ domains blocked out of the box (Hagezi Pro blocklist)
  • Subdomain matching, one-click allowlist from dashboard, pause/toggle
  • Auto-downloads on startup, refreshes every 24h
  • Correct responses: 0.0.0.0 for A queries, :: for AAAA queries

Live Dashboard (http://localhost:5380)

  • Total queries, cache hit rate, blocked count, active overrides, uptime
  • Resolution path breakdown, scrolling query log with "allow" buttons
  • Override management: create, edit (click to prefill), delete
  • Blocking toggle and pause button in the header

System DNS Integration

  • numa install — saves current DNS for all network services, sets to 127.0.0.1
  • numa uninstall — restores original DNS from ~/.numa/original-dns.json
  • Auto-discovery — parses scutil --dns on macOS to find Tailscale/VPN forwarding rules
  • Numa can be a drop-in system DNS replacement without breaking existing routing

Developer Overrides

  • Ephemeral DNS overrides with auto-expiry via REST API
  • Auto-reverts when time expires — no cleanup needed

REST API (18 endpoints)

Category Endpoints
Dashboard GET /
Overrides POST/GET/DELETE /overrides, POST /overrides/environment, GET/DELETE /overrides/{domain}
Blocking GET /blocking/stats, PUT /blocking/toggle, POST /blocking/pause, GET/POST /blocking/allowlist, DELETE /blocking/allowlist/{domain}
Diagnostics GET /diagnose/{domain}, GET /query-log, GET /stats
Cache GET/DELETE /cache, DELETE /cache/{domain}
Health GET /health

Performance

  • Cached lookups: 0ms (sub-millisecond)
  • Blocklist check: zero-allocation hot path, ~200ns per query
  • Forwarding rule matching: pre-computed suffixes, zero-allocation
  • Blocklist reload: parsed outside lock, swapped in sub-microsecond

Also Included

  • Startup banner with colored system info
  • CI via GitHub Actions (clippy + fmt + build)
  • Multi-stage Dockerfile (scratch-based)
  • MIT license, benchmark script, development plan docs

How It Compares

Pi-hole NextDNS Cloudflare Numa
Ad blocking Limited ✓ 385K+ domains
Portable ✗ (Raspberry Pi) Cloud only Cloud only ✓ Single binary
Developer overrides ✓ REST API + auto-expiry
Data stays local ✗ Cloud ✗ Cloud ✓ 100% local
System DNS install Manual N/A N/A numa install/uninstall
Live dashboard ✓ Real-time + controls

Test Plan

  • cargo build && sudo cargo run — startup banner shows all info
  • Open http://localhost:5380 — dashboard loads
  • dig @127.0.0.1 ads.google.com0.0.0.0 (blocked)
  • dig @127.0.0.1 google.com → resolves normally
  • Dashboard: allow/pause/toggle blocking controls work
  • Dashboard: create/edit/delete overrides
  • numa install — sets system DNS, saves backup to ~/.numa/
  • numa uninstall — restores original DNS, removes backup
  • numa help — shows usage
  • Tailscale domains auto-route to correct upstream
  • make all passes (clippy + fmt)

🤖 Generated with Claude Code

## What is Numa? **DNS you own. Everywhere you go.** A portable DNS resolver with ad blocking, developer overrides, and a live dashboard — in a single binary. No Raspberry Pi, no cloud account, no data leaving your machine. ## Quick Start ```bash cargo build sudo cargo run # binds to port 53, downloads blocklists on first run ``` Open **http://localhost:5380** for the live dashboard. ```bash dig @127.0.0.1 google.com # ✓ resolves normally dig @127.0.0.1 ads.google.com # ✗ blocked → 0.0.0.0 ``` Set Numa as your system DNS: ```bash sudo numa install # saves current DNS, sets to 127.0.0.1 sudo numa uninstall # restores original settings ``` ## What's New ### Ad Blocking - **385K+ domains** blocked out of the box (Hagezi Pro blocklist) - Subdomain matching, one-click allowlist from dashboard, pause/toggle - Auto-downloads on startup, refreshes every 24h - Correct responses: `0.0.0.0` for A queries, `::` for AAAA queries ### Live Dashboard (`http://localhost:5380`) - Total queries, cache hit rate, blocked count, active overrides, uptime - Resolution path breakdown, scrolling query log with "allow" buttons - Override management: create, edit (click to prefill), delete - Blocking toggle and pause button in the header ### System DNS Integration - **`numa install`** — saves current DNS for all network services, sets to `127.0.0.1` - **`numa uninstall`** — restores original DNS from `~/.numa/original-dns.json` - **Auto-discovery** — parses `scutil --dns` on macOS to find Tailscale/VPN forwarding rules - Numa can be a drop-in system DNS replacement without breaking existing routing ### Developer Overrides - Ephemeral DNS overrides with auto-expiry via REST API - Auto-reverts when time expires — no cleanup needed ### REST API (18 endpoints) | Category | Endpoints | |---|---| | Dashboard | `GET /` | | Overrides | `POST/GET/DELETE /overrides`, `POST /overrides/environment`, `GET/DELETE /overrides/{domain}` | | Blocking | `GET /blocking/stats`, `PUT /blocking/toggle`, `POST /blocking/pause`, `GET/POST /blocking/allowlist`, `DELETE /blocking/allowlist/{domain}` | | Diagnostics | `GET /diagnose/{domain}`, `GET /query-log`, `GET /stats` | | Cache | `GET/DELETE /cache`, `DELETE /cache/{domain}` | | Health | `GET /health` | ### Performance - Cached lookups: **0ms** (sub-millisecond) - Blocklist check: zero-allocation hot path, ~200ns per query - Forwarding rule matching: pre-computed suffixes, zero-allocation - Blocklist reload: parsed outside lock, swapped in sub-microsecond ### Also Included - Startup banner with colored system info - CI via GitHub Actions (clippy + fmt + build) - Multi-stage Dockerfile (scratch-based) - MIT license, benchmark script, development plan docs ## How It Compares | | Pi-hole | NextDNS | Cloudflare | Numa | |---|---|---|---|---| | Ad blocking | ✓ | ✓ | Limited | ✓ 385K+ domains | | Portable | ✗ (Raspberry Pi) | Cloud only | Cloud only | ✓ Single binary | | Developer overrides | ✗ | ✗ | ✗ | ✓ REST API + auto-expiry | | Data stays local | ✓ | ✗ Cloud | ✗ Cloud | ✓ 100% local | | System DNS install | Manual | N/A | N/A | ✓ `numa install/uninstall` | | Live dashboard | ✓ | ✓ | ✗ | ✓ Real-time + controls | ## Test Plan - [ ] `cargo build && sudo cargo run` — startup banner shows all info - [ ] Open `http://localhost:5380` — dashboard loads - [ ] `dig @127.0.0.1 ads.google.com` → `0.0.0.0` (blocked) - [ ] `dig @127.0.0.1 google.com` → resolves normally - [ ] Dashboard: allow/pause/toggle blocking controls work - [ ] Dashboard: create/edit/delete overrides - [ ] `numa install` — sets system DNS, saves backup to `~/.numa/` - [ ] `numa uninstall` — restores original DNS, removes backup - [ ] `numa help` — shows usage - [ ] Tailscale domains auto-route to correct upstream - [ ] `make all` passes (clippy + fmt) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.