
Rust製のポータブルDNSリゾルバ — .numaローカルドメイン、広告ブロッキング、開発者オーバーライド
# 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)** で封じることで、誰があなたであるかと何を尋ねたかを単一の当事者が同時に見ることはありません — すべてラップトップから、クラウドアカウントも Raspberry Pi も不要です。
Rust でゼロから構築。DNS ライブラリは一切使用していません。キャッシュ、広告ブロック、ローカルサービスドメインを標準搭載。オプションでルートネームサーバーからの再帰解決と完全な DNSSEC 信頼チェーン検証、さらに暗号化されたクライアント接続(iOS Private DNS、systemd-resolved など)のための DNS-over-TLS リスナーを提供します。`numa relay` を実行すれば、同じバイナリがパブリック ODoH エンドポイントにもなります — 厳選された DNSCrypt リストには現在 1 つのリレーしか生き残っていないため、Numa のデプロイごとにエコシステムが実質的に拡大します。約 8MB のバイナリ 1 つに、すべてが埋め込まれています。ワイヤプロトコルパーサーは学習プロジェクトとして手書きされました。その後の機能(再帰リゾルバ、DNSSEC、ダッシュボード)は AI の支援を受けて構築されました。

## クイックスタート
コミットメントの度合いが低い順に 3 つの方法があります。システム DNS を変更するのは 3 番目だけです。
### 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` を開き、任意のユーザー名とそのトークンでログインします。ポート 5553 は、ホスト上で既に 53 を占有しているものを回避します。ホストが DNS に Numa を使っていないため、ここでは `numa.numa` は解決されません。`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` はこの 3 つすべてを元に戻します。インストール後、ダッシュボードは **http://numa.numa** でも利用できます。
macOS と Linux では、numa はシステムサービス(launchd/systemd)として実行されます。systemd ユニットは非特権(`DynamicUser=yes`、`CAP_NET_BIND_SERVICE` のみ)です。launchd デーモンは root として実行されます。`numa install` は systemd-resolved を drop-in 経由で再設定し、`numa uninstall` がそれを削除します。ポート 53 を保持している他のプロセス(NetworkManager のものを含む dnsmasq)は手動で停止する必要があります。Windows では、numa はレジストリ経由でログイン時に自動起動します。Windows はさらに `127.0.0.2:53` をバインドし(組み込みの Dnscache が `127.0.0.1:53` を所有しているため)、クエリをそこへルーティングする NRPT ルールをインストールします — そのため `bind_addr`/`api_bind_addr` は `127.0.0.1` ではなく `127.0.0.2` に対して編集してください。
### ログイン
ループバック(`localhost`、`127.0.0.1`、`numa.numa`)経由ではログインは不要です。それ以外(Docker のポートマッピングやこのマシンの LAN アドレスを含む)では、Numa が初回起動時に生成する API トークンが要求されます。`sudo numa token`(Windows では管理者シェル)で表示し、任意のユーザー名でログインします。`[server] api_token` または `NUMA_API_TOKEN` で独自のトークンを固定できます。
### すべての痕跡を削除する
`uninstall` は DNS を復元しますが、データディレクトリは保持するため、再インストールしても同じトークンと 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) による広告とトラッカーのブロック。毎日更新されます。どのネットワークでも動作します — カフェ、ホテル、空港。ラップトップと一緒に持ち運べます。
3 つの解決モード:
- **`forward`**(デフォルト) — 既存のシステム DNS への透過プロキシ。すべてがこれまで通り動作し、その上にキャッシュと広告ブロックが加わります。キャプティブポータル、VPN、企業 DNS — すべて尊重されます。
- **`recursive`** — ルートネームサーバーから直接解決。上流への依存がなく、単一の主体があなたの完全なクエリパターンを見ることはありません。完全な信頼チェーン検証には `[dnssec] enabled = true` を追加します。
- **`auto`** — 起動時にルートサーバーをプローブし、到達可能なら再帰、そうでなければ Quad9(`https://9.9.9.9/dns-query`)への DoH 経由でフォワードし、Quad9 があなたのクエリを見ることになります。別のプロバイダを選ぶには、独自の `[upstream]` で `forward` を使用してください。
DNSSEC は完全な信頼チェーンを検証します: RRSIG 署名、DNSKEY 検証、DS 委任、NSEC/NSEC3 否定証明。[仕組みを読む →](https://numa.rs/blog/posts/dnssec-from-scratch.html)
**DNS-over-TLS リスナー**(RFC 7858) — iOS Private DNS、systemd-resolved、stubby などの厳格なクライアントからポート 853 で暗号化クエリを受け付けます。2 つのモード:
- **自己署名**(デフォルト) — 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` を公開信頼された証明書(例: numa インスタンスを指すドメインでの DNS-01 チャレンジによる Let's Encrypt)に向けます。クライアントはトラストストアの設定なしで接続します — AdGuard Home や Cloudflare `1.1.1.1` と同じ UX です。
ALPN `"dot"` は両モードでアドバタイズされ強制されます。ALPN が一致しないハンドシェイクは、クロスプロトコル混同への防御として拒否されます。
**Oblivious DoH**(RFC 9230) — `[upstream] mode = "odoh"`([レシピ](https://github.com/razvandimescu/numa/blob/main/recipes/odoh-upstream.md))では、各クエリがターゲットへ HPKE で封じられ、リレーを通じて送信されます。リレーはあなたの IP と暗号文を見て、ターゲットは質問とリレーの IP を見て、クエリをリダイレクトするリレーは新しい宛先に復号できないものを渡します。Numa は登録可能ドメインを共有するリレーとターゲットを拒否します。ODoH はその後に開く接続を隠しません: ISP は依然として宛先 IP を見て、ECH がなければ TLS ハンドシェイクのホスト名も見ます。
**スマートフォンのセットアップ** — iPhone または Android を 1 ステップで Numa に向けます:
```bash
numa setup-phone
```
QR コードを表示します。スキャンして、プロファイルをインストールし、証明書の信頼を有効にすると、スマートフォンの 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` で有効化します。
**ハブモード**: 1 つのインスタンスを `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` のトークンでログインします。`numa-data` ボリュームは、コンテナを再作成してもトークンとローカル 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 リレー、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 管理 UI | フル | フル | — | ダッシュボード |
| 暗号化上流(DoH/DoT) | cloudflared が必要 | DoH のみ | DoT のみ | DoH + DoT(`tls://`) |
| 暗号化クライアント(DoT リスナー) | stunnel サイドカーが必要 | あり | あり | ネイティブ(RFC 7858) |
| DoH サーバーエンドポイント | — | あり | — | あり(RFC 8484) |
| リクエストヘッジング | — | — | — | 全プロトコル(UDP、DoH、DoT) |
| Serve-stale + プリフェッチ | — | — | TTL 90% でプリフェッチ | RFC 8767、TTL 90% でプリフェッチ |
| 条件付きフォワーディング | — | あり | あり | あり(サフィックス別ルール) |
| ポータブル(ラップトップ) | いいえ(アプライアンス) | いいえ(アプライアンス) | サーバー | 単一バイナリ、macOS/Linux/Windows |
| コミュニティの成熟度 | 56K スター、10 年 | 33K スター | 20 年 | 新規 |
## Numa をプライマリ DNS として実行する
**Numa が停止した場合。** `numa install` は Numa を launchd(macOS)または systemd(Linux)に登録し、終了時に再起動します。Numa が唯一のリゾルバである場合、再起動するまで DNS ルックアップは失敗します。Numa の使用をやめてマシンの以前の DNS 設定を復元するには、`sudo numa uninstall` を実行します。
Numa が実行中でも上流に到達できない場合、TTL を最大 1 時間超えてキャッシュされた回答を返すことができます(RFC 8767)。キャッシュされていない名前は失敗します。
**テスト。** DNS パーサーは、それに触れるすべてのプルリクエストでファジングされ、毎週より長時間の実行が行われます([`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 — 匿名化クライアントモード + パブリックリレー(`numa relay`、RFC 9230)
- [x] 再帰解決 + DNSSEC — 信頼チェーン、NSEC/NSEC3
- [x] SRTT ベースのネームサーバー選択
- [x] マルチフォワーダーフェイルオーバー — SRTT ランキング付きの複数上流、フォールバックプール
- [x] リクエストヘッジング — 並列リクエストがパケットロスとテールレイテンシを救済(全プロトコル)
- [x] Serve-stale + プリフェッチ — RFC 8767、TTL 10% 未満および stale 提供時のバックグラウンド更新
- [x] 条件付きフォワーディング — スプリットホライズン DNS 用のサフィックス別ルール(Tailscale、VPN)
- [x] キャッシュウォーミング — 設定されたドメインのプロアクティブ解決
- [x] モバイルオンボーディング — `setup-phone` QR フロー、モバイル API、mobileconfig プロファイル
- [ ] pkarr 統合 — Mainline DHT 経由の自己主権 DNS
- [ ] グローバル `.numa` ネーム — DHT ベース、レジストラ不要
## ライセンス
MIT