# Numa
[](https://github.com/razvandimescu/numa/actions)
[](https://crates.io/crates/numa)
[](LICENSE)
**属于你的 DNS。无论你走到哪里。** — [numa.rs](https://numa.rs)
一个单二进制文件的可移植 DNS 解析器。在任何网络上屏蔽广告,为你的本地服务命名(`frontend.numa`),用自动回滚覆盖任何主机名,并用 **ODoH (RFC 9230)** 封装每一个出站查询,这样就没有任何一方能同时看到你是谁以及你查询了什么——这一切都在你的笔记本电脑上完成,无需云账户或树莓派。
用 Rust 从零构建。零 DNS 库。开箱即用的缓存、广告拦截和本地服务域名。可选的从根域名服务器开始的递归解析,带完整的 DNSSEC 信任链验证,外加一个用于加密客户端连接的 DNS-over-TLS 监听器(iOS 私人 DNS、systemd-resolved 等)。运行 `numa relay`,同一个二进制文件也能变成一个公共 ODoH 端点——精选的 DNSCrypt 列表目前只有一个存活的 relay,所以每一次 Numa 部署都实实在在地扩展了这个生态。一个约 8MB 的二进制文件,一切内嵌。线协议解析器是作为学习项目手写的;后续功能(递归解析器、DNSSEC、仪表盘)是在 AI 辅助下构建的。

## 快速开始
三种入门方式,从最轻到最重。只有第三种会更改你的系统 DNS。
### 1. 在 Docker 中试用(主机上不安装任何东西)
```bash
docker run -d --name numa -p 127.0.0.1:5553:53/udp -p 127.0.0.1:5553:53/tcp \
-p 127.0.0.1:5380:5380 ghcr.io/razvandimescu/numa
dig @127.0.0.1 -p 5553 example.com
docker exec numa numa token # dashboard password
```
打开 `http://localhost:5380`,用任意用户名和该 token 登录。端口 5553 绕开了主机上任何已占用 53 端口的程序。`numa.numa` 在这里无法解析,因为主机并未使用 Numa 作为 DNS。用 `docker rm -f numa` 清理。
### 2. 在前台运行(系统 DNS 不受影响)
安装二进制文件:
```bash
# macOS
brew install razvandimescu/tap/numa
# Linux
curl -fsSL https://raw.githubusercontent.com/razvandimescu/numa/main/install.sh | sh
# Arch Linux
pacman -S numa
# Windows — download from GitHub Releases
# All platforms
cargo install numa
# Nix
nix run github:razvandimescu/numa
```
```bash
sudo numa # Ctrl-C to stop (port 53 requires root/admin)
```
Numa 监听 53 端口,但在你运行 `numa install` 之前,你的系统会保持当前的解析器,所以用 `dig @127.0.0.1 example.com` 测试。仪表盘位于 `http://localhost:5380`。如果 53 端口被占用(Ubuntu/Mint 上的 systemd-resolved),在 [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml) 中设置 `bind_addr`,或使用 Docker。
### 3. 设为系统 DNS
| 平台 | 安装 | 卸载 |
|----------|---------|-----------|
| macOS | `sudo numa install` | `sudo numa uninstall` |
| Linux | `sudo numa install` | `sudo numa uninstall` |
| Windows | `numa install`(管理员)+ 重启 | `numa uninstall`(管理员)+ 重启 |
`install` 会注册一个服务,将系统 DNS 指向 Numa 并信任其本地 CA。`uninstall` 会撤销这三项。安装后,仪表盘也位于 **http://numa.numa**。
在 macOS 和 Linux 上,numa 作为系统服务运行(launchd/systemd)。systemd 单元是非特权的(`DynamicUser=yes`,仅 `CAP_NET_BIND_SERVICE`);launchd 守护进程以 root 运行。`numa install` 通过一个 drop-in 重新配置 systemd-resolved,`numa uninstall` 会将其移除;任何其他占用 53 端口的进程(dnsmasq,包括 NetworkManager 的)都必须手动停止。在 Windows 上,numa 通过注册表在登录时自动启动。Windows 还会绑定 `127.0.0.2:53`(内置的 Dnscache 占用 `127.0.0.1:53`)并安装一条 NRPT 规则将查询路由到它——所以请针对 `127.0.0.2` 而非 `127.0.0.1` 编辑 `bind_addr`/`api_bind_addr`。
### 登录
通过回环地址(`localhost`、`127.0.0.1`、`numa.numa`)无需登录。其他任何方式,包括 Docker 端口映射或本机的 LAN 地址,都会被要求提供 API token,Numa 在首次启动时生成它。用 `sudo numa token`(Windows 上为管理员 shell)打印它,然后用任意用户名登录。用 `[server] api_token` 或 `NUMA_API_TOKEN` 固定你自己的 token。
### 移除所有痕迹
`uninstall` 会恢复 DNS,但保留数据目录,所以重新安装会保留相同的 token 和 CA。要移除一切,先卸载,然后删除:
| 平台 | 遗留内容 |
|----------|-------------|
| macOS | 二进制文件、`/usr/local/var/numa`、`/usr/local/var/log/numa.log` |
| Linux | 二进制文件(来自 install.sh 的 `/usr/local/bin/numa`)、`/var/lib/numa`、`/etc/numa` |
| Windows | 二进制文件、`%PROGRAMDATA%\numa` |
包管理器安装的版本用 `brew uninstall`、`pacman -R` 或 `cargo uninstall` 移除二进制文件。在 Linux 上,`numa install` 会将一个无法从其原始位置(例如 `~/.cargo/bin`)运行的二进制文件复制到 `/usr/local/bin/numa`,卸载会留下该副本。
如果你创建了用户配置,也请删除 `~/.config/numa`,以及你设置的 `[server] data_dir` 路径。
## 本地服务
为你的开发服务命名,而不是记住端口号:
```bash
curl -X POST localhost:5380/services \
-d '{"name":"frontend","target_port":5173}'
```
现在 `https://frontend.numa` 在你的浏览器中就能用了——绿色锁标、有效证书、用于 HMR 的 WebSocket 透传。无需 mkcert,无需 nginx,无需 `/etc/hosts`。
添加基于路径的路由(`app.numa/api → :5001`),通过 LAN 发现跨机器共享服务,或在 [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml) 中配置一切。
## 广告拦截与隐私
通过 [Hagezi Pro](https://github.com/hagezi/dns-blocklists) 进行广告和跟踪器拦截,每日刷新。在任何网络上都能用——咖啡馆、酒店、机场。随你的笔记本电脑同行。
三种解析模式:
- **`forward`**(默认)——透明代理到你现有的系统 DNS。一切照旧,只是在其上叠加了缓存和广告拦截。强制门户、VPN、企业 DNS——全部得到尊重。
- **`recursive`**——直接从根域名服务器解析。无上游依赖,没有任何单一实体能看到你的完整查询模式。添加 `[dnssec] enabled = true` 以进行完整的信任链验证。
- **`auto`**——启动时探测根服务器,可达则递归,否则通过 DoH 转发到 Quad9(`https://9.9.9.9/dns-query`),后者会看到你的查询。使用 `forward` 搭配你自己的 `[upstream]` 来选择不同的提供商。
DNSSEC 验证完整的信任链:RRSIG 签名、DNSKEY 验证、DS 委派、NSEC/NSEC3 否认证明。[阅读其工作原理 →](https://numa.rs/blog/posts/dnssec-from-scratch.html)
**DNS-over-TLS 监听器**(RFC 7858)——在 853 端口接受来自严格客户端的加密查询,如 iOS 私人 DNS、systemd-resolved 或 stubby。两种模式:
- **自签名**(默认)——numa 自动生成一个本地 CA。`numa install` 会将其添加到 macOS、Linux(Debian/Ubuntu、Fedora/RHEL/SUSE、Arch)和 Windows 的系统信任存储中,`numa uninstall` 会将其移除。在 iOS 上,从 `numa setup-phone` 安装 `.mobileconfig`。Firefox 保留自己的 NSS 存储并忽略系统存储——如果你需要在 Firefox 中为 `.numa` 服务使用 HTTPS,请在那里手动信任该 CA。
- **自带证书**——将 `[dot] cert_path` / `key_path` 指向一个公开受信任的证书(例如,通过 DNS-01 挑战在一个指向你的 numa 实例的域名上使用 Let's Encrypt)。客户端无需任何信任存储设置即可连接——与 AdGuard Home 或 Cloudflare `1.1.1.1` 相同的用户体验。
两种模式下都会通告并强制 ALPN `"dot"`;ALPN 不匹配的握手会被拒绝,作为跨协议混淆防御。
**Oblivious DoH**(RFC 9230)——使用 `[upstream] mode = "odoh"`([配方](https://github.com/razvandimescu/numa/blob/main/recipes/odoh-upstream.md))时,每个查询都用 HPKE 密封到目标并通过一个 relay 发送。relay 看到你的 IP 和密文,目标看到问题和 relay 的 IP,而重定向查询的 relay 交给新目的地的东西它无法解密。Numa 拒绝共享同一可注册域名的 relay 和目标。ODoH 不会隐藏你之后打开的连接:你的 ISP 仍然能看到目标 IP,并且在没有 ECH 的情况下,还能看到 TLS 握手里的主机名。
**手机设置**——一步将你的 iPhone 或 Android 指向 Numa:
```bash
numa setup-phone
```
打印一个二维码。扫描它,安装配置文件,切换证书信任——你手机的 DNS 现在通过 TLS 路由到 Numa。需要在 `numa.toml` 中设置 `[mobile] enabled = true`。
## LAN 发现
在多台机器上运行 Numa。它们通过 mDNS 自动找到彼此:
```
Machine A (192.168.1.5) Machine B (192.168.1.20)
┌──────────────────────┐ ┌──────────────────────┐
│ Numa │ mDNS │ Numa │
│ - api (port 8000) │◄───────────►│ - grafana (3000) │
│ - frontend (5173) │ discovery │ │
└──────────────────────┘ └──────────────────────┘
```
从 Machine B:`curl http://api.numa` → 代理到 Machine A 的 8000 端口。用 `numa lan on` 启用。
**Hub 模式**:运行一个实例,设置 `bind_addr = "0.0.0.0:53"`,并将其他设备的 DNS 指向它——它们无需安装任何东西就能获得广告拦截 + `.numa` 解析。`bind_addr` 也接受一个列表来绑定特定的接口子集。分步指南:[全网配方](https://github.com/razvandimescu/numa/blob/main/recipes/network-wide.md)。
## Docker
```bash
# Recommended — host networking (Linux)
docker run -d --name numa --network host -v numa-data:/var/lib/numa ghcr.io/razvandimescu/numa
# Port mapping (macOS/Windows Docker Desktop)
docker run -d --name numa -p 53:53/udp -p 53:53/tcp -p 5380:5380 -v numa-data:/var/lib/numa ghcr.io/razvandimescu/numa
```
仪表盘位于 `http://localhost:5380`。使用端口映射或从另一台设备访问时,用任意用户名和来自 `docker exec numa numa token` 的 token 登录。`numa-data` 卷在容器重建之间保留 token 和本地 CA。镜像默认将 API 和代理绑定到 `0.0.0.0`。用自定义配置覆盖:
```bash
docker run -d --name numa --network host -v numa-data:/var/lib/numa \
-v /path/to/numa.toml:/root/.config/numa/numa.toml \
ghcr.io/razvandimescu/numa
```
多架构:`linux/amd64` 和 `linux/arm64`。
开箱即用的 compose 配方:
- [`packaging/client/`](https://github.com/razvandimescu/numa/blob/main/packaging/client) — ODoH 客户端模式(匿名 DNS),Numa + 入门 `numa.toml`。
- [`packaging/relay/`](https://github.com/razvandimescu/numa/blob/main/packaging/relay) — 公共 ODoH relay,Numa + Caddy + ACME。
## 对比
| | Pi-hole | AdGuard Home | Unbound | Numa |
|---|---|---|---|---|
| 本地服务代理 + 自动 TLS | — | — | — | `.numa` 域名、HTTPS、WebSocket |
| LAN 服务发现 | — | — | — | mDNS,零配置 |
| 开发者覆盖(REST API) | — | — | — | 自动回滚,可脚本化 |
| 递归解析器 | — | — | 是 | 是,带 SRTT 选择 |
| DNSSEC 验证 | — | — | 是 | 是(RSA、ECDSA、Ed25519) |
| 广告拦截 | 是 | 是 | — | Hagezi Pro |
| 按客户端规则 | 分组 | 是 | Views / tags | 按 CIDR(`[[client_policy]]`),仅配置文件 |
| Web 管理界面 | 完整 | 完整 | — | 仪表盘 |
| 加密上游(DoH/DoT) | 需要 cloudflared | 仅 DoH | 仅 DoT | DoH + DoT(`tls://`) |
| 加密客户端(DoT 监听器) | 需要 stunnel sidecar | 是 | 是 | 原生(RFC 7858) |
| DoH 服务器端点 | — | 是 | — | 是(RFC 8484) |
| 请求对冲 | — | — | — | 所有协议(UDP、DoH、DoT) |
| Serve-stale + 预取 | — | — | 在 90% TTL 时预取 | RFC 8767,在 90% TTL 时预取 |
| 条件转发 | — | 是 | 是 | 是(按后缀规则) |
| 可移植(笔记本电脑) | 否(专用设备) | 否(专用设备) | 服务器 | 单二进制文件,macOS/Linux/Windows |
| 社区成熟度 | 56K stars,10 年 | 33K stars | 20 年 | 新 |
## 将 Numa 作为你的主 DNS 运行
**如果 Numa 停止。** `numa install` 会将 Numa 注册到 launchd(macOS)或 systemd(Linux),它们会在其退出时重启它。如果 Numa 是你唯一的解析器,DNS 查询会失败直到它重启。要停止使用 Numa 并恢复机器之前的 DNS 设置,运行 `sudo numa uninstall`。
如果 Numa 正在运行但上游不可达,它可以在超过 TTL 后最多一小时内提供缓存答案(RFC 8767)。它未缓存的名称会失败。
**测试。** DNS 解析器在每一个触及它的 pull request 上都会进行模糊测试,每周有更长时间的运行([`fuzz.yml`](https://github.com/razvandimescu/numa/blob/main/.github/workflows/fuzz.yml))。CI 对依赖运行 `cargo audit`,并在 macOS 和 Linux 上安装、重装和卸载 Numa。
**解析器加固。** 在递归模式下,Numa 会丢弃所查询区域之外的答案记录,拒绝查询位于私有或回环地址的名称服务器,并限制每次查找的上游查询数和委派深度。通过纯 UDP 的回复必须匹配查询的随机事务 ID 和问题。ANY 查询会被拒绝。DNSSEC 验证默认关闭;`numa dnssec on` 会打开它。要私下报告漏洞,请参阅 [SECURITY.md](https://github.com/razvandimescu/numa/blob/main/SECURITY.md)。
**Numa 不做什么。** 没有 DHCP,没有集群,实例之间没有配置同步。大多数设置位于 [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml) 中,而非仪表盘。对于整个网络,请在一台保持开机的机器上运行它。
## 性能
缓存查询 0.1ms——与 Unbound 和 AdGuard Home 相当。线级缓存存储原始字节并就地修补 TTL。请求对冲消除了 p99 尖峰:冷递归 p99 538ms 对比 Unbound 748ms(−28%),σ 紧 4 倍。[基准测试 →](https://github.com/razvandimescu/numa/blob/main/benches)
## 了解更多
- [博客:将 Numa 作为你的 tailnet 解析器](https://numa.rs/blog/posts/numa-tailnet-resolver.html)
- [博客:用 Rust 从零实现 DNS-over-TLS](https://numa.rs/blog/posts/dot-from-scratch.html)
- [博客:用 Rust 从零实现 DNSSEC](https://numa.rs/blog/posts/dnssec-from-scratch.html)
- [博客:我从零构建了一个 DNS 解析器](https://numa.rs/blog/posts/dns-from-scratch.html)
- [配置参考](https://github.com/razvandimescu/numa/blob/main/numa.toml) — 所有选项均有内联文档;`numa config path` 显示你的安装正在使用哪个文件,`numa config edit` 打开它
- [REST API](https://github.com/razvandimescu/numa/blob/main/src/api.rs) — 覆盖、缓存、拦截、服务、诊断
- [numa-metrics](https://github.com/razvandimescu/numa-metrics) — 持久查询历史与分析,设计上位于主机之外(无 SD 卡写入)
## 路线图
- [x] DNS 转发、缓存、广告拦截、开发者覆盖
- [x] `.numa` 本地域名 — 自动 TLS、路径路由、WebSocket 代理
- [x] LAN 服务发现 — mDNS、跨机器 DNS + 代理
- [x] DNS-over-HTTPS — 加密上游 + 服务器端点(RFC 8484)
- [x] DNS-over-TLS — 加密客户端监听器(RFC 7858)+ 上游转发(`tls://`)
- [x] Oblivious DoH — 匿名客户端模式 + 公共 relay(`numa relay`,RFC 9230)
- [x] 递归解析 + DNSSEC — 信任链、NSEC/NSEC3
- [x] 基于 SRTT 的名称服务器选择
- [x] 多转发器故障转移 — 多个上游带 SRTT 排名、回退池
- [x] 请求对冲 — 并行请求挽救丢包和尾部延迟(所有协议)
- [x] Serve-stale + 预取 — RFC 8767,在 <10% TTL 时和 stale 服务时后台刷新
- [x] 条件转发 — 用于分裂地平线 DNS(Tailscale、VPN)的按后缀规则
- [x] 缓存预热 — 对配置的域名进行主动解析
- [x] 移动端入门 — `setup-phone` 二维码流程、移动 API、mobileconfig 配置文件
- [ ] pkarr 集成 — 通过 Mainline DHT 实现自主主权 DNS
- [ ] 全局 `.numa` 名称 — DHT 支持,无需注册商
## 许可证
MIT