
مُحلّل DNS محمول بلغة Rust — نطاقات .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، بالإضافة إلى مستمع DNS-over-TLS لاتصالات العملاء المشفرة (iOS Private DNS، systemd-resolved، إلخ). شغِّل `numa relay` فيصبح نفس الملف التنفيذي نقطة نهاية ODoH عامة أيضًا — قائمة DNSCrypt المنسقة تحتوي حاليًا على مُرحِّل واحد فقط صامد، لذا فإن كل نشر لـ Numa يوسّع المنظومة بشكل ملموس. ملف تنفيذي واحد بحجم ~8MB، كل شيء مضمّن بداخله. كُتب محلل بروتوكول الشبكة يدويًا كمشروع تعليمي؛ أما الميزات اللاحقة (المحلل العودي، DNSSEC، لوحة التحكم) فقد بُنيت بمساعدة الذكاء الاصطناعي.

## البدء السريع
ثلاث طرق للدخول، من الأقل إلى الأكثر التزامًا. الطريقة الثالثة فقط هي التي تغيّر 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` وسجّل الدخول بأي اسم مستخدم وذلك الرمز. المنفذ 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 محتلًا (systemd-resolved على Ubuntu/Mint)، اضبط `bind_addr` في [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml) أو استخدم Docker.
### 3. التعيين كـ DNS النظام
| المنصة | التثبيت | الإلغاء |
|----------|---------|-----------|
| macOS | `sudo numa install` | `sudo numa uninstall` |
| Linux | `sudo numa install` | `sudo numa uninstall` |
| Windows | `numa install` (admin) + reboot | `numa uninstall` (admin) + reboot |
`install` يسجّل خدمة، ويوجّه DNS النظام إلى Numa ويثق بشهادته المحلية CA. `uninstall` يعكس الثلاثة جميعًا. بمجرد التثبيت، تكون لوحة التحكم أيضًا على **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 (dnsmasq، بما في ذلك الخاص بـ NetworkManager) يجب إيقافها يدويًا. على Windows، يبدأ numa تلقائيًا عند تسجيل الدخول عبر السجل. كما يربط Windows `127.0.0.2:53` (لأن Dnscache المدمج يملك `127.0.0.1:53`) ويثبّت قاعدة NRPT لتوجيه الاستعلامات إليه — لذا عدّل `bind_addr`/`api_bind_addr` مقابل `127.0.0.2`، وليس `127.0.0.1`.
### تسجيل الدخول
عبر loopback (`localhost`، `127.0.0.1`، `numa.numa`) لا حاجة لتسجيل الدخول. أي شيء آخر، بما في ذلك تعيين منفذ Docker أو عنوان LAN لهذا الجهاز، يُطلب منه رمز API، الذي يولّده Numa عند أول تشغيل. اطبعه باستخدام `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 | الملف التنفيذي (`/usr/local/bin/numa` من install.sh)، `/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` في متصفحك — قفل أخضر، شهادة صالحة، تمرير WebSocket لـ HMR. لا 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 نظامك الحالي. كل شيء يعمل كما كان، فقط مع التخزين المؤقت وحجب الإعلانات فوقه. البوابات المقيدة، VPNs، DNS المؤسسي — كلها محترمة.
- **`recursive`** — الحل مباشرة من خوادم الأسماء الجذرية. لا اعتماد على خادم أعلى، ولا كيان واحد يرى نمط استعلاماتك الكامل. أضف `[dnssec] enabled = true` للتحقق الكامل من سلسلة الثقة.
- **`auto`** — يفحص الخوادم الجذرية عند بدء التشغيل، عودي إذا كان يمكن الوصول، وإلا forward عبر 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 Private DNS، أو systemd-resolved، أو stubby. وضعان:
- **موقّع ذاتيًا** (افتراضي) — يولّد numa شهادة CA محلية تلقائيًا. يضيفها `numa install` إلى مخزن ثقة النظام على macOS، وLinux (Debian/Ubuntu، Fedora/RHEL/SUSE، Arch)، وWindows، ويزيلها `numa uninstall`. على iOS، ثبّت ملف `.mobileconfig` من `numa setup-phone`. يحتفظ Firefox بمخزن NSS الخاص به ويتجاهل مخزن النظام — ثق بـ CA هناك يدويًا إذا كنت بحاجة إلى HTTPS لخدمات `.numa` في Firefox.
- **أحضر شهادتك الخاصة** — وجّه `[dot] cert_path` / `key_path` إلى شهادة موثوقة علنًا (مثل Let's Encrypt عبر تحدي DNS-01 على نطاق يشير إلى نسخة numa الخاصة بك). يتصل العملاء دون أي إعداد لمخزن الثقة — نفس تجربة AdGuard Home أو Cloudflare `1.1.1.1`.
يُعلَن عن ALPN `"dot"` ويُفرض في كلا الوضعين؛ يُرفض أي handshake بـ ALPN غير مطابق كدفاع ضد الخلط بين البروتوكولات.
**Oblivious DoH** (RFC 9230) — مع `[upstream] mode = "odoh"` ([الوصفة](https://github.com/razvandimescu/numa/blob/main/recipes/odoh-upstream.md)) يُختَم كل استعلام بـ HPKE نحو الهدف ويُرسَل عبر مُرحِّل. يرى المُرحِّل عنوان IP الخاص بك والنص المشفّر، ويرى الهدف السؤال وعنوان IP الخاص بالمُرحِّل، والمُرحِّل الذي يعيد توجيه الاستعلام يسلّم الوجهة الجديدة شيئًا لا يمكنه فك تشفيره. يرفض Numa أي مُرحِّل وهدف يتشاركان نطاقًا قابلًا للتسجيل. لا يخفي ODoH الاتصال الذي تفتحه بعد ذلك: لا يزال مزوّد خدمة الإنترنت يرى عنوان IP للوجهة، وبدون ECH، اسم المضيف في handshake TLS.
**إعداد الهاتف** — وجّه iPhone أو Android الخاص بك إلى Numa في خطوة واحدة:
```bash
numa setup-phone
```
يطبع رمز QR. امسحه، ثبّت الملف الشخصي، فعّل ثقة الشهادة — أصبح DNS هاتفك الآن يمر عبر Numa باستخدام TLS. يتطلب `[mobile] enabled = true` في `numa.toml`.
## اكتشاف 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 │ │
└──────────────────────┘ └──────────────────────┘
```
من الجهاز B: `curl http://api.numa` ← يُمرَّر عبر وكيل إلى المنفذ 8000 على الجهاز A. فعّله باستخدام `numa lan on`.
**وضع المحور**: شغّل نسخة واحدة مع `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]]`)، ملف الإعدادات فقط |
| واجهة إدارة ويب | كاملة | كاملة | — | لوحة تحكم |
| خادم أعلى مشفّر (DoH/DoT) | يحتاج cloudflared | DoH فقط | DoT فقط | DoH + DoT (`tls://`) |
| عملاء مشفّرون (مستمع DoT) | يحتاج stunnel sidecar | نعم | نعم | أصلي (RFC 7858) |
| نقطة نهاية خادم DoH | — | نعم | — | نعم (RFC 8484) |
| Request hedging | — | — | — | كل البروتوكولات (UDP، DoH، DoT) |
| Serve-stale + prefetch | — | — | Prefetch عند 90% TTL | RFC 8767، prefetch عند 90% TTL |
| التمرير الشرطي | — | نعم | نعم | نعم (قواعد لكل لاحقة) |
| محمول (حاسوب محمول) | لا (جهاز مخصص) | لا (جهاز مخصص) | خادم | ملف تنفيذي واحد، 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 الخاص بها (RFC 8767). الأسماء غير المخزّنة مؤقتًا تفشل.
**الاختبار.** يخضع محلل DNS لاختبار fuzzing في كل pull request يمسّه، مع تشغيلات أطول أسبوعيًا ([`fuzz.yml`](https://github.com/razvandimescu/numa/blob/main/.github/workflows/fuzz.yml)). يشغّل CI أمر `cargo audit` على التبعيات ويثبّت ويعيد تثبيت ويلغي تثبيت Numa على macOS وLinux.
**تقوية المحلل.** في الوضع العودي، يُسقط Numa سجلات الإجابة خارج المنطقة المستعلَمة، ويرفض الاستعلام من خوادم أسماء على عناوين خاصة أو loopback، ويحدّ من استعلامات كل عملية بحث وعدد الإحالات. يجب أن تطابق الردود عبر UDP العادي معرّف المعاملة العشوائي والسؤال. تُرفض استعلامات ANY. التحقق من DNSSEC معطّل افتراضيًا؛ `numa dnssec on` يفعّله. للإبلاغ عن ثغرة بشكل خاص، راجع [SECURITY.md](https://github.com/razvandimescu/numa/blob/main/SECURITY.md).
**ما لا يفعله Numa.** لا DHCP، لا clustering، لا مزامنة إعدادات بين النسخ. معظم الإعدادات موجودة في [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml)، وليس في لوحة التحكم. لشبكة كاملة، شغّله على جهاز يبقى قيد التشغيل.
## الأداء
0.1ms للاستعلامات المخزّنة مؤقتًا — يضاهي Unbound وAdGuard Home. يخزّن المخزن المؤقت على مستوى الشبكة البايتات الخام مع تعديل TTL في مكانه. يلغي Request hedging ارتفاعات 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)
- [المدونة: DNS-over-TLS من الصفر في Rust](https://numa.rs/blog/posts/dot-from-scratch.html)
- [المدونة: تنفيذ DNSSEC من الصفر في Rust](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] Request hedging — طلبات متوازية تنقذ من فقدان الحزم وزمن الاستجابة الطرفي (كل البروتوكولات)
- [x] Serve-stale + prefetch — RFC 8767، تحديث في الخلفية عند <10% TTL وعند التقديم من المخزون القديم
- [x] التمرير الشرطي — قواعد لكل لاحقة لـ split-horizon DNS (Tailscale، VPNs)
- [x] تسخين المخزن المؤقت — حل استباقي للنطاقات المُهيّأة
- [x] الإعداد للجوال — تدفق QR لـ `setup-phone`، API الجوال، ملفات mobileconfig الشخصية
- [ ] تكامل pkarr — DNS سيادي ذاتيًا عبر Mainline DHT
- [ ] أسماء `.numa` عالمية — مدعومة بـ DHT، بدون مسجّل
## الترخيص
MIT