chore: remove docs from git tracking (already gitignored)
docs/ is in .gitignore but files were force-added. Remove from tracking — files remain on disk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
# Windows Support — Implementation Plan
|
||||
|
||||
*March–April 2026*
|
||||
|
||||
## Phase 1: Run on Windows without system integration — DONE (v0.3.0)
|
||||
|
||||
- [x] Cross-platform `config_dir()` and `data_dir()`
|
||||
- [x] `src/system_dns.rs` — Windows DNS discovery via `ipconfig /all`
|
||||
- [x] Stubs for install/uninstall/service on unsupported OS
|
||||
- [x] Multicast LAN discovery (`SO_REUSEPORT` skipped on Windows)
|
||||
- [x] All deps compile on windows-msvc
|
||||
- [x] CI: `check-windows` job (build + clippy)
|
||||
- [x] Cross-platform LAN discovery tested: macOS ↔ Windows
|
||||
|
||||
## Phase 2: DNS configuration — DONE (PR #28)
|
||||
|
||||
- [x] `numa install` — set DNS to 127.0.0.1 via `netsh` for all active interfaces
|
||||
- [x] `numa uninstall` — restore DNS from backup (DHCP or static with secondaries)
|
||||
- [x] `ipconfig /all` parser — per-interface adapter name, DHCP status, DNS servers
|
||||
- [x] Localization — German adapter/DHCP/DNS labels handled
|
||||
- [x] Disconnected adapters — skipped
|
||||
- [x] Backup — `%PROGRAMDATA%\numa\original-dns.json`
|
||||
- [x] Dnscache — disable via registry on install, re-enable on uninstall (reboot required)
|
||||
- [x] Auto-start — registry Run key (`HKLM\...\Run\Numa`) on install, removed on uninstall
|
||||
- [x] UDP ConnectionReset — Windows ICMP error 10054 caught and ignored
|
||||
- [x] IP validation — added to `discover_windows()`
|
||||
- [x] CI: `cargo test` + binary artifact upload on Windows
|
||||
- [ ] `README.md` — add Windows install instructions
|
||||
|
||||
## Phase 3: Full service integration (future)
|
||||
|
||||
### Windows Service
|
||||
|
||||
- Use `windows-service` crate to register Numa as a Windows Service
|
||||
- `sc.exe create numa binPath=...` as alternative
|
||||
- Auto-start on boot (SYSTEM context, no login required), auto-restart on crash
|
||||
- Replace registry Run key with proper SCM integration
|
||||
|
||||
### CA trust
|
||||
|
||||
- `certutil.exe -addstore Root ca.pem` to trust Numa CA system-wide
|
||||
- Reverse: `certutil.exe -delstore Root "Numa Local CA"`
|
||||
- Needs admin elevation
|
||||
|
||||
### DHCP DNS detection
|
||||
|
||||
- Current `detect_dhcp_dns()` returns `None` on Windows
|
||||
- Could parse `ipconfig /all` for "DHCP Server" + "DNS Servers" lines
|
||||
- Or use WinAPI `GetNetworkParams()`
|
||||
@@ -1,620 +0,0 @@
|
||||
# Launch Drafts
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
**r/selfhosted** (0 upvotes, hostile) — "replaces Pi-hole" framing triggered
|
||||
defensive comparisons. Audience protects their stack.
|
||||
|
||||
**r/programare** (26 upvotes, 22 comments, 12K views, 90.6% ratio) — worked
|
||||
because it led with technical achievement. But: "what does this offer over
|
||||
/etc/hosts?" and "mature solutions exist (dnsmasq, nginx)" were the top
|
||||
objections. Tool-replacement angle falls flat with generalist audiences.
|
||||
|
||||
**r/webdev** — removed by moderators (self-promotion rules).
|
||||
|
||||
Key takeaways:
|
||||
|
||||
- Lead with what's *unique*, not what it *replaces*
|
||||
- Write like explaining to a colleague, not marketing copy
|
||||
- Pick ONE hook per community — don't try to be everything
|
||||
- Triple-check the GitHub link works before posting
|
||||
- Authentic tone > polished bullets
|
||||
- Agree with "just use X" — then show what X can't do
|
||||
- Don't oversell the pkarr/token vision — one sentence max
|
||||
- Benchmark request from r/programare (Mydocalm) — warm follow-up content
|
||||
|
||||
---
|
||||
|
||||
## Launch Order
|
||||
|
||||
~~0. **r/programare** — done (2026-03-21). 12K views, 26 upvotes, 22 comments.~~
|
||||
~~1. **r/webdev** — removed by moderators.~~
|
||||
|
||||
~~2. **r/degoogle** — done~~
|
||||
~~3. **r/node** — done~~
|
||||
|
||||
4. **r/coolgithubprojects** — zero friction, just post the repo
|
||||
~~5. **r/sideproject** — done (2026-03-29)~~
|
||||
6. **r/dns** — technical DNS audience, recursive + DNSSEC angle
|
||||
7. **Show HN** — Tuesday-Thursday, 9-10 AM ET
|
||||
8. **r/rust** — same day as HN, technical deep-dive
|
||||
9. **r/commandline** — 24h after HN
|
||||
10. **r/selfhosted** — only if HN hits front page, lead with recursive + LAN discovery
|
||||
11. **r/programare follow-up** — benchmark post + recursive/DNSSEC update
|
||||
|
||||
---
|
||||
|
||||
## Community Drafts
|
||||
|
||||
### Show HN
|
||||
|
||||
**Title (72 chars):**
|
||||
Show HN: I built a DNS resolver from scratch in Rust – no DNS libraries
|
||||
|
||||
**Body:**
|
||||
|
||||
I wanted to understand how DNS actually works at the wire level, so I built
|
||||
a resolver from scratch. No dns libraries — the RFC 1035 protocol (headers,
|
||||
labels, compression pointers, record types) is all hand-parsed. It started
|
||||
as a learning project and turned into something I use daily as my system DNS.
|
||||
|
||||
What it does today:
|
||||
|
||||
- **Forward mode by default** — transparent proxy to your existing DNS with
|
||||
caching and ad blocking. Changes nothing about your network.
|
||||
- **Full recursive resolver** — set `mode = "recursive"` and it resolves from
|
||||
root nameservers. No upstream dependency. CNAME chasing, TLD priming, SRTT.
|
||||
- **DNSSEC validation** — chain-of-trust verification from root KSK.
|
||||
RSA/SHA-256, ECDSA P-256, Ed25519. Sets the AD bit on verified responses.
|
||||
- **Ad blocking** — ~385K+ domains via Hagezi Pro, works on any network
|
||||
- **DNS-over-HTTPS** — encrypted upstream (Quad9, Cloudflare, or any
|
||||
provider) as an alternative to recursive mode
|
||||
- **`.numa` local domains** — register `frontend.numa → localhost:5173` and
|
||||
it creates both the DNS record and an HTTP/HTTPS reverse proxy with
|
||||
auto-generated TLS certs. WebSocket passthrough works (Vite HMR).
|
||||
- **LAN service discovery** — run Numa on two machines, they find each other
|
||||
via UDP multicast. Zero config.
|
||||
- **Developer overrides** — point any hostname to any IP, auto-reverts
|
||||
after N minutes. REST API for scripting.
|
||||
|
||||
Single binary, macOS + Linux. `sudo numa install` and it's your system DNS —
|
||||
forward mode by default, recursive when you're ready.
|
||||
|
||||
The interesting technical bits: the recursive resolver walks root → TLD →
|
||||
authoritative with iterative queries, caching NS/DS/DNSKEY records at each
|
||||
hop. DNSSEC validation verifies RRSIG signatures against DNSKEY, walks the
|
||||
chain via DS records up to the hardcoded root trust anchor. ECDSA P-256
|
||||
verification takes 174ns (benchmarked with criterion). Cold-cache validation
|
||||
for a new domain is ~90ms, with only 1 network fetch needed (TLD chain is
|
||||
pre-warmed on startup). SRTT-based nameserver selection learns which
|
||||
servers respond fastest — average recursive query drops from 2.8s to
|
||||
237ms after warmup (12x).
|
||||
|
||||
It also handles hostile networks: if your ISP blocks UDP port 53,
|
||||
Numa detects this after 3 failures and switches all
|
||||
queries to TCP automatically. Resets when you change networks. RFC 7816
|
||||
query minimization means root servers only see the TLD, not your full
|
||||
query.
|
||||
|
||||
The DNS cache adjusts TTLs on read (remaining time, not original). Each
|
||||
query is an async tokio task. EDNS0 with DO bit and 1232-byte payload
|
||||
(DNS Flag Day 2020).
|
||||
|
||||
Longer term I want to add pkarr/DHT resolution for self-sovereign DNS,
|
||||
but that's future work.
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/rust
|
||||
|
||||
**Title:** I built a recursive DNS resolver from scratch in Rust — DNSSEC, no DNS libraries
|
||||
|
||||
**Body:**
|
||||
|
||||
I've been building a DNS resolver in Rust as a learning project that became
|
||||
my daily driver. The entire DNS wire protocol is implemented by hand —
|
||||
no `trust-dns`, no `hickory-dns`, no `simple-dns`. Headers, label sequences,
|
||||
compression pointers, EDNS, all of it.
|
||||
|
||||
Some things I found interesting while building this:
|
||||
|
||||
**Recursive resolution** — iterative queries from root hints, walking
|
||||
root → TLD → authoritative. CNAME chasing, A+AAAA glue extraction from
|
||||
additional sections, referral depth limits. TLD priming pre-warms NS + DS +
|
||||
DNSKEY for 34 gTLDs + EU ccTLDs on startup.
|
||||
|
||||
**DNSSEC chain-of-trust** — the most involved part. Verify RRSIG signatures
|
||||
against DNSKEY, walk DS records up to the hardcoded root KSK (key tag 20326).
|
||||
Uses `ring` for crypto: RSA/SHA-256, ECDSA P-256 (174ns per verify), Ed25519.
|
||||
RFC 3110 RSA keys need converting to PKCS#1 DER for ring — wrote an ASN.1
|
||||
encoder for that. RRSIG time validity checks per RFC 4035 §5.3.1.
|
||||
|
||||
**NSEC/NSEC3 denial proofs** — proving a name *doesn't* exist is harder than
|
||||
proving it does. NSEC uses canonical DNS name ordering to prove gap coverage.
|
||||
NSEC3 uses iterated SHA-1 hashing + base32hex + a 3-part closest encloser
|
||||
proof (RFC 5155 §8.4). Both require authority-section RRSIG verification.
|
||||
|
||||
**Wire protocol parsing** — DNS uses a binary format with label compression
|
||||
(pointers back into the packet via 2-byte offsets). Parsing this correctly
|
||||
is surprisingly tricky because pointers can chain. I use a `BytePacketBuffer`
|
||||
that tracks position and handles jumps.
|
||||
|
||||
**Performance** — TLD chain pre-warming means cold-cache DNSSEC validation
|
||||
needs ~1 DNSKEY fetch (down from 5). Referral DS piggybacking caches DS
|
||||
from authority sections during resolution. ECDSA P-256 verify: 174ns.
|
||||
RSA/SHA-256: 10.9µs. DS verify: 257ns.
|
||||
|
||||
**LAN service discovery** — Numa instances on the same network find each
|
||||
other via UDP multicast. The tricky part was self-filtering: I initially
|
||||
filtered by IP, but two instances on the same host share an IP. Switched to
|
||||
a per-process instance ID (`pid ^ nanos`).
|
||||
|
||||
**Auto TLS** — generates a local CA + per-service certs using `rcgen`.
|
||||
`numa install` trusts the CA in the OS keychain. HTTPS proxy via `rustls` +
|
||||
`tokio-rustls`.
|
||||
|
||||
Single binary, no runtime dependencies. Uses `tokio`, `axum` (REST
|
||||
API/dashboard), `hyper` (reverse proxy), `ring` (DNSSEC crypto), `reqwest`
|
||||
(DoH), `socket2` (multicast), `rcgen` + `rustls` (TLS).
|
||||
|
||||
Happy to discuss any of the implementation decisions.
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/degoogle
|
||||
|
||||
**Title:** I replaced cloud DNS with a recursive resolver — resolves from root, no upstream, DNSSEC
|
||||
|
||||
**Body:**
|
||||
|
||||
I wanted a DNS setup with zero cloud dependency. No NextDNS account,
|
||||
no Cloudflare dashboard, no Pi-hole appliance, no upstream resolver seeing
|
||||
my queries. Just a single binary on my laptop that resolves everything
|
||||
itself.
|
||||
|
||||
Built one in Rust. What it does:
|
||||
|
||||
- **Forward mode by default** — transparent proxy to your existing DNS with
|
||||
caching and ad blocking. Changes nothing about your network.
|
||||
- **Recursive resolution** — set `mode = "recursive"` and it resolves directly
|
||||
from root nameservers. No Quad9, no Cloudflare, no upstream dependency.
|
||||
Each authoritative server only sees the query for its zone — no single
|
||||
entity sees your full browsing pattern.
|
||||
- **DNSSEC validation** — verifies the chain of trust from root KSK.
|
||||
Responses are cryptographically verified — no one can tamper with them
|
||||
in transit.
|
||||
- **System-level ad blocking** — Hagezi Pro list (~385K+ domains),
|
||||
works on any network. Coffee shop WiFi, airport, hotel.
|
||||
- **ISP resistant** — in recursive mode, if UDP is blocked Numa switches
|
||||
to TCP automatically. Or set `mode = "auto"` to probe on startup and
|
||||
fall back to encrypted DoH if needed.
|
||||
- **Query minimization** — root servers only see the TLD (.com), not
|
||||
your full domain. RFC 7816.
|
||||
- **Zero telemetry, zero cloud** — all data stays on your machine. No
|
||||
account, no login, no analytics. Config is a single TOML file.
|
||||
- **Local service naming** — bonus for developers: `https://app.numa`
|
||||
instead of `localhost:3000`, with auto-generated TLS certs
|
||||
|
||||
Single binary, macOS + Linux. `sudo numa install` and it's your system
|
||||
DNS — forward mode by default, recursive when you're ready. No Docker,
|
||||
no PHP, no external dependencies.
|
||||
|
||||
The DNS wire protocol is parsed from scratch — no DNS libraries. You can
|
||||
read every line of code.
|
||||
|
||||
```
|
||||
brew install razvandimescu/tap/numa
|
||||
# or
|
||||
cargo install numa
|
||||
```
|
||||
|
||||
MIT license. https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/node
|
||||
|
||||
**Title:** I replaced localhost:5173 with frontend.numa — auto HTTPS, HMR works, no nginx
|
||||
|
||||
**Body:**
|
||||
|
||||
Running a Vite frontend on :5173, Express API on :3000, maybe docs on
|
||||
:4000 — I could never remember which port was which. And CORS between
|
||||
`localhost:5173` and `localhost:3000` is its own special hell.
|
||||
|
||||
How do you get named domains with HTTPS locally?
|
||||
|
||||
1. /etc/hosts + mkcert + nginx
|
||||
2. dnsmasq + mkcert + Caddy
|
||||
3. `sudo numa`
|
||||
|
||||
What it actually does:
|
||||
|
||||
```
|
||||
curl -X POST localhost:5380/services \
|
||||
-d '{"name":"frontend","target_port":5173}'
|
||||
```
|
||||
|
||||
Now `https://frontend.numa` works in my browser. Green lock, valid cert.
|
||||
|
||||
- **HMR works** — Vite, webpack, socket.io all pass through the proxy.
|
||||
No special config.
|
||||
- **CORS solved** — `frontend.numa` and `api.numa` share the `.numa`
|
||||
cookie domain. Cross-service auth just works.
|
||||
- **Path routing** — `app.numa/api → :3000`, `app.numa/auth → :3001`.
|
||||
Like nginx location blocks, zero config files.
|
||||
|
||||
No mkcert, no nginx.conf, no Caddyfile, no editing /etc/hosts.
|
||||
Single binary, one command.
|
||||
|
||||
```
|
||||
brew install razvandimescu/tap/numa
|
||||
# or
|
||||
cargo install numa
|
||||
```
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/dns
|
||||
|
||||
**Title:** Numa — recursive DNS resolver from scratch in Rust, DNSSEC, no DNS libraries
|
||||
|
||||
**Body:**
|
||||
|
||||
I built a recursive DNS resolver where the entire wire protocol (RFC 1035 —
|
||||
headers, label compression, EDNS0) is hand-parsed. No `hickory-dns`,
|
||||
no `trust-dns`.
|
||||
|
||||
What it does:
|
||||
- Full recursive resolver from root hints (iterative queries, no upstream needed)
|
||||
- DNSSEC chain-of-trust validation (RSA/SHA-256, ECDSA P-256, Ed25519)
|
||||
- EDNS0 with DO bit, 1232-byte payload (DNS Flag Day 2020 compliant)
|
||||
- DNS-over-HTTPS as an alternative upstream mode
|
||||
- Ad blocking (~385K+ domains via Hagezi Pro)
|
||||
- Conditional forwarding (auto-detects Tailscale/VPN split-DNS)
|
||||
- Local zones, ephemeral overrides with auto-revert via REST API
|
||||
|
||||
DNSSEC implementation: DNSKEY/DS/RRSIG record parsing, canonical wire format
|
||||
for signed data, key tag computation (RFC 4034), DS digest verification.
|
||||
Chain walks from zone → TLD → root trust anchor. ECDSA P-256 signature
|
||||
verification in 174ns. TLD chain pre-warmed on startup. Referral DS records
|
||||
piggybacked from authority sections during resolution.
|
||||
|
||||
NSEC/NSEC3 authenticated denial of existence: NXDOMAIN gap proofs, NSEC3
|
||||
closest encloser proofs (3-part per RFC 5155), NODATA type absence proofs,
|
||||
authority-section RRSIG verification. Iteration cap at 500 for NSEC3 DoS
|
||||
prevention.
|
||||
|
||||
What it doesn't do (yet): no authoritative zone serving (AXFR/NOTIFY).
|
||||
|
||||
Single binary, macOS + Linux. MIT license.
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### Lobsters (invite-only)
|
||||
|
||||
**Title:** Numa — DNS resolver from scratch in Rust, no DNS libraries
|
||||
|
||||
**Body:**
|
||||
|
||||
I built a DNS resolver in Rust — RFC 1035 wire protocol parsed by hand,
|
||||
no `trust-dns` or `hickory-dns`. Started as a learning project, became
|
||||
my daily system DNS.
|
||||
|
||||
Beyond resolving, it does local `.numa` domains with auto HTTPS reverse
|
||||
proxy (register `frontend.numa → localhost:5173`, get a green lock and
|
||||
WebSocket passthrough), and LAN service discovery via UDP multicast —
|
||||
two machines running Numa find each other's services automatically.
|
||||
|
||||
Implementation bits I found interesting: DNS label compression (chained
|
||||
2-byte pointers back into the packet), browsers rejecting wildcard certs
|
||||
under single-label TLDs (`*.numa` fails — need per-service SANs), and
|
||||
`SO_REUSEPORT` on macOS for multiple processes binding the same multicast
|
||||
port.
|
||||
|
||||
Set `mode = "recursive"` for DNSSEC-validated resolution from root
|
||||
nameservers — no upstream, no middleman.
|
||||
|
||||
Single binary, macOS + Linux.
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/coolgithubprojects
|
||||
|
||||
**Post type:** Image post with `hero-demo.gif`, GitHub link in first comment.
|
||||
|
||||
**Title:** Numa — portable DNS resolver built from scratch in Rust. Ad blocking, local HTTPS domains, LAN discovery, recursive resolution with DNSSEC. Single binary.
|
||||
|
||||
**First comment (post immediately):**
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
```
|
||||
brew install razvandimescu/tap/numa && sudo numa
|
||||
```
|
||||
|
||||
No DNS libraries — RFC 1035 wire protocol parsed by hand.
|
||||
Recursive resolution from root nameservers with full DNSSEC
|
||||
chain-of-trust validation. 385K+ blocked ad domains.
|
||||
.numa local domains with auto TLS and WebSocket proxy.
|
||||
|
||||
---
|
||||
|
||||
### r/sideproject
|
||||
|
||||
**Title:** I built a DNS resolver from scratch in Rust — it's now my daily system DNS
|
||||
|
||||
**Body:**
|
||||
|
||||
Last year I wanted to understand how DNS actually works at the wire
|
||||
level, so I started parsing RFC 1035 packets by hand. No DNS libraries,
|
||||
no trust-dns, no hickory-dns — just bytes and the spec.
|
||||
|
||||
It turned into something I use every day. What it does now:
|
||||
|
||||
- **Ad blocking** on any network (coffee shops, airports) — 385K+
|
||||
domains blocked, travels with my laptop
|
||||
- **Local service naming** — `https://frontend.numa` instead of
|
||||
`localhost:5173`, with auto-generated TLS certs and WebSocket
|
||||
passthrough for HMR
|
||||
- **Recursive resolution** from root nameservers with DNSSEC
|
||||
chain-of-trust validation — set `mode = "recursive"` for full
|
||||
privacy, no upstream dependency, no single entity sees my query
|
||||
pattern
|
||||
- **LAN discovery** — two machines running Numa find each other's
|
||||
services automatically via mDNS
|
||||
|
||||
Single Rust binary, ~8MB, MIT license. `sudo numa install` and it's your
|
||||
system DNS — caching, ad blocking, .numa domains, zero config changes.
|
||||
|
||||
I wrote about the technical journey here:
|
||||
- [I Built a DNS Resolver from Scratch](https://numa.rs/blog/posts/dns-from-scratch.html)
|
||||
- [Implementing DNSSEC from Scratch](https://numa.rs/blog/posts/dnssec-from-scratch.html)
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/webdev (Showoff Saturday — posted 2026-03-28)
|
||||
|
||||
**Title:** I replaced localhost:5173 with frontend.numa — shared cookie domain, auto HTTPS, no nginx
|
||||
|
||||
**Body:**
|
||||
|
||||
The port numbers weren't the real problem. It was CORS between
|
||||
`localhost:5173` and `localhost:3000`, Secure cookies not setting over
|
||||
HTTP, and service workers requiring a secure context.
|
||||
|
||||
I built a DNS resolver that gives local services named domains under a
|
||||
shared TLD:
|
||||
|
||||
```
|
||||
curl -X POST localhost:5380/services \
|
||||
-d '{"name":"frontend","target_port":5173}'
|
||||
```
|
||||
|
||||
Now `https://frontend.numa` and `https://api.numa` share the `.numa`
|
||||
cookie domain. Cross-service auth just works. Secure cookies set.
|
||||
Service workers run.
|
||||
|
||||
What's under the hood:
|
||||
- **Auto HTTPS** — generates a local CA + per-service TLS certs. Green
|
||||
lock, no mkcert.
|
||||
- **WebSocket passthrough** — Vite/webpack HMR goes through the proxy.
|
||||
No special config.
|
||||
- **Path routing** — `app.numa/api → :3000`, `app.numa/auth → :3001`.
|
||||
Like nginx location blocks.
|
||||
- **Also a full DNS resolver** — forward mode with caching and ad
|
||||
blocking by default. Set `mode = "recursive"` for full DNSSEC-validated
|
||||
resolution from root nameservers.
|
||||
|
||||
Single Rust binary. `sudo numa install` and it's your system DNS — caching,
|
||||
ad blocking, .numa domains. No nginx, no Caddy, no /etc/hosts.
|
||||
|
||||
```
|
||||
brew install razvandimescu/tap/numa
|
||||
# or
|
||||
cargo install numa
|
||||
```
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
**Lessons from r/node (2026-03-24):** "Can't remember 3 ports?" got
|
||||
pushback — the CORS/cookie angle resonated more. Lead with what you
|
||||
can't do without it, not what's annoying.
|
||||
|
||||
---
|
||||
|
||||
### r/commandline
|
||||
|
||||
**Title:** numa — local dev DNS with auto HTTPS and LAN service discovery, single Rust binary
|
||||
|
||||
**Body:**
|
||||
|
||||
I run 5-6 local services and wanted named domains with HTTPS instead of
|
||||
remembering port numbers. Built a DNS resolver that handles `.numa`
|
||||
domains:
|
||||
|
||||
```
|
||||
curl -X POST localhost:5380/services \
|
||||
-d '{"name":"api","target_port":8000}'
|
||||
```
|
||||
|
||||
Now `https://api.numa` resolves, proxies to localhost:8000, and has a
|
||||
valid TLS cert. WebSocket passthrough works — Vite HMR goes through
|
||||
the proxy fine.
|
||||
|
||||
The part I didn't expect to be useful: LAN service discovery. Two
|
||||
machines running numa find each other via UDP multicast. I register
|
||||
`api.numa` on my laptop, my teammate's numa instance picks it up
|
||||
automatically. Zero config.
|
||||
|
||||
Also blocks ~385K+ ad domains since it's already your DNS resolver.
|
||||
Portable — works on any network (coffee shops, airports). Set
|
||||
`mode = "recursive"` for full DNSSEC-validated resolution from root
|
||||
nameservers — no upstream dependency.
|
||||
|
||||
```
|
||||
brew install razvandimescu/tap/numa
|
||||
sudo numa
|
||||
```
|
||||
|
||||
Single binary, DNS wire protocol parsed from scratch (no DNS libraries).
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
### r/selfhosted (only if Show HN hits front page)
|
||||
|
||||
**Title:** Numa — recursive resolver + ad blocking + LAN service discovery in one binary
|
||||
|
||||
**Body:**
|
||||
|
||||
I built a DNS resolver in Rust that I've been running as my system DNS.
|
||||
Two features I'm most proud of:
|
||||
|
||||
**Recursive resolution + DNSSEC** — set `mode = "recursive"` and it resolves
|
||||
from root nameservers, no upstream dependency. Chain-of-trust verification
|
||||
(RSA, ECDSA, Ed25519), NSEC/NSEC3 denial proofs. No single entity sees your
|
||||
full query pattern — each authoritative server only sees its zone's queries.
|
||||
|
||||
**LAN service discovery** — I register `api.numa → localhost:8000` on my
|
||||
laptop. My colleague's machine, also running Numa, picks it up via UDP
|
||||
multicast — `api.numa` resolves to my IP on his machine. Zero config.
|
||||
|
||||
The rest of what it does:
|
||||
- **Ad blocking** — ~385K+ domains (Hagezi Pro), portable. Works on any
|
||||
network including coffee shops and airports.
|
||||
- **DNS-over-HTTPS** — encrypted upstream as an alternative to recursive mode.
|
||||
- **Auto HTTPS for local services** — generates a local CA + per-service
|
||||
TLS certs. `https://frontend.numa` with a green lock, WebSocket passthrough.
|
||||
- **Hub mode** — point other devices' DNS to it, they get ad blocking +
|
||||
`.numa` resolution without installing anything.
|
||||
|
||||
Replaces Pi-hole + Unbound in one binary. No Raspberry Pi, no Docker, no PHP.
|
||||
|
||||
Single binary, macOS + Linux. Config is one optional TOML file.
|
||||
|
||||
**What it doesn't do (yet):** No web-based config editor (TOML + REST API).
|
||||
DoT listener is in progress.
|
||||
|
||||
`brew install razvandimescu/tap/numa` or `cargo install numa`
|
||||
|
||||
https://github.com/razvandimescu/numa
|
||||
|
||||
---
|
||||
|
||||
## Preparation Checklist
|
||||
|
||||
- [ ] Verify GitHub repo is PUBLIC before any post
|
||||
- [ ] Build some comment history on posting account first
|
||||
- [ ] Post HN Tuesday-Thursday, 9-10 AM Eastern
|
||||
- [ ] Respond to every comment within 2 hours for the first 6 hours
|
||||
- [ ] Have fixes ready to ship within 24h for reported issues
|
||||
- [ ] Don't oversell the pkarr/token vision — one sentence max
|
||||
|
||||
## Rules
|
||||
|
||||
- Verify GitHub repo is PUBLIC before every post
|
||||
- Use an account with comment history, not a fresh one
|
||||
- Respond to every comment within 2 hours
|
||||
- Never be defensive — acknowledge valid criticism, redirect
|
||||
- If someone says "just use X" — agree it works, explain what's *uniquely different*
|
||||
- Lead with unique capabilities, not tool replacement
|
||||
|
||||
---
|
||||
|
||||
## Prepared Responses
|
||||
|
||||
**"What does this offer over /etc/hosts?"** *(actual r/programare objection)*
|
||||
/etc/hosts is static and per-machine. Numa gives you: auto-revert after N
|
||||
minutes (great for testing), a REST API so scripts can create/remove entries,
|
||||
HTTPS reverse proxy with auto TLS, and LAN discovery so you don't have to
|
||||
edit hosts on every device. Different tools for different problems.
|
||||
|
||||
**"Mature solutions already exist (dnsmasq, nginx, etc.)"** *(actual r/programare objection)*
|
||||
Absolutely — and they're great. The thing they don't do: register a service
|
||||
on machine A and have it automatically appear on machine B via multicast.
|
||||
Numa integrates DNS + reverse proxy + TLS + discovery into one binary so
|
||||
those pieces work together. If you only need DNS forwarding, dnsmasq is the
|
||||
right tool.
|
||||
|
||||
**"Why not Pi-hole / AdGuard Home?"**
|
||||
They're network appliances — need dedicated hardware or Docker. Numa is a
|
||||
single binary on your laptop. When you move to a coffee shop, your ad
|
||||
blocking comes with you. Plus the reverse proxy + LAN discovery.
|
||||
|
||||
**"Why from scratch / no DNS libraries?"**
|
||||
Started as a learning project to understand the wire protocol. Turned out
|
||||
having full control over the pipeline makes features like conditional
|
||||
forwarding and override injection trivial — they're just steps in the
|
||||
resolution chain.
|
||||
|
||||
**"Vibe coded / AI generated?"**
|
||||
I use AI as a coding partner — same as using Stack Overflow or pair
|
||||
programming. I make the architecture decisions, direct what gets built,
|
||||
and review everything. The DNS wire protocol parser was the original
|
||||
learning project I wrote by hand. Later features were built collaboratively
|
||||
with AI assistance. You can read every line — nothing is opaque generated
|
||||
slop.
|
||||
|
||||
**"Why sudo / why port 53?"**
|
||||
Port 53 requires root on Unix. Numa only needs it for the UDP socket.
|
||||
You can also bind to a high port for testing: `bind_addr = "127.0.0.1:5353"`.
|
||||
|
||||
**"What about .numa TLD conflicts?"**
|
||||
The TLD is configurable in `numa.toml`. If `.numa` ever becomes official,
|
||||
change it to anything else.
|
||||
|
||||
**"Does it support DoH/DoT?"**
|
||||
DoH is built in — set `address = "https://9.9.9.9/dns-query"` in
|
||||
`[upstream]` and your queries are encrypted. Or set `mode = "auto"` to
|
||||
probe root servers and fall back to DoH if blocked. DoT listener support
|
||||
is in progress (PR #25).
|
||||
|
||||
**"But Quad9/Cloudflare still sees my queries"**
|
||||
In forward mode (the default), yes — your upstream resolver sees your queries.
|
||||
Set `mode = "recursive"` and Numa resolves directly from root nameservers —
|
||||
no single upstream sees your full query pattern. Each authoritative server
|
||||
only sees the query relevant to its zone. Add `[dnssec] enabled = true` to
|
||||
cryptographically verify responses.
|
||||
|
||||
**"Show me benchmarks / performance numbers"** *(actual r/programare request)*
|
||||
Benchmark suite is in `benches/` (criterion). Cached round-trip: 691ns.
|
||||
Pipeline throughput: ~2.0M qps. DNSSEC: ECDSA P-256 verify 174ns, RSA/SHA-256
|
||||
10.9µs, DS verify 257ns. Cold-cache DNSSEC validation ~90ms (1 network fetch,
|
||||
TLD chain pre-warmed). Full comparison against system resolver, Quad9,
|
||||
Cloudflare, Google on the site.
|
||||
|
||||
**"Why not just use Unbound?"**
|
||||
Numa supports recursive resolution with DNSSEC validation, same as Unbound
|
||||
(`mode = "recursive"`). The difference:
|
||||
Numa also has built-in ad blocking, a dashboard, `.numa` local domains with
|
||||
auto HTTPS, LAN service discovery, and developer overrides. Unbound does
|
||||
one thing well; Numa integrates six features into one binary.
|
||||
|
||||
**"Why not Technitium?"**
|
||||
Technitium is the closest in features — recursive, DNSSEC, ad blocking,
|
||||
dashboard. Good tool. Two differences: (1) Numa is a single static binary,
|
||||
Technitium requires the .NET runtime; (2) Numa has developer tooling that
|
||||
Technitium doesn't — `.numa` local domains with auto TLS reverse proxy,
|
||||
path-based routing, LAN service discovery, ephemeral overrides with
|
||||
auto-revert. Different audiences: Technitium targets server admins, Numa
|
||||
targets developers on laptops.
|
||||
|
||||
**"Does it support Windows?"**
|
||||
Yes. `numa install` in an admin terminal sets system DNS and auto-starts
|
||||
numa on boot. Requires a reboot (Windows DNS Client holds port 53 at kernel
|
||||
level). `numa uninstall` restores everything. Native Windows Service
|
||||
integration is next.
|
||||
Reference in New Issue
Block a user