
Resolvedor DNS portátil en Rust — dominios locales .numa, bloqueo de anuncios, anulaciones para desarrolladores
# Numa
[](https://github.com/razvandimescu/numa/actions)
[](https://crates.io/crates/numa)
[](LICENSE)
**DNS que es tuyo. Dondequiera que vayas.** — [numa.rs](https://numa.rs)
Un resolutor DNS portátil en un solo binario. Bloquea anuncios en cualquier red, nombra tus servicios locales (`frontend.numa`), sobrescribe cualquier nombre de host con reversión automática y sella cada consulta saliente con **ODoH (RFC 9230)** para que ninguna entidad vea tanto quién eres como qué preguntaste — todo desde tu portátil, sin cuenta en la nube ni Raspberry Pi.
Construido desde cero en Rust. Cero librerías DNS. Caché, bloqueo de anuncios y dominios de servicios locales listos para usar. Resolución recursiva opcional desde los servidores de nombres raíz con validación completa de cadena de confianza DNSSEC, más un listener DNS-over-TLS para conexiones de clientes cifradas (iOS Private DNS, systemd-resolved, etc.). Ejecuta `numa relay` y el mismo binario se convierte también en un endpoint ODoH público — la lista curada de DNSCrypt actualmente tiene un solo relay superviviente, así que cada despliegue de Numa expande materialmente el ecosistema. Un binario de ~8MB, todo embebido. El parser del protocolo de red fue escrito a mano como proyecto de aprendizaje; las características posteriores (resolutor recursivo, DNSSEC, dashboard) fueron construidas con asistencia de IA.

## Inicio Rápido
Tres formas de entrar, de menor a mayor compromiso. Solo la tercera cambia el DNS de tu sistema.
### 1. Pruébalo en Docker (nada instalado en el host)
```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
```
Abre `http://localhost:5380` e inicia sesión con cualquier nombre de usuario y ese token. El puerto 5553 evita lo que ya esté ocupando el 53 en el host. `numa.numa` no resolverá aquí, porque el host no está usando Numa para DNS. Limpia con `docker rm -f numa`.
### 2. Ejecutar en primer plano (DNS del sistema intacto)
Instala el binario:
```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 escucha en el puerto 53, pero tu sistema mantiene su resolutor actual hasta que ejecutes `numa install`, así que prueba con `dig @127.0.0.1 example.com`. El dashboard está en `http://localhost:5380`. Si el puerto 53 está ocupado (systemd-resolved en Ubuntu/Mint), configura `bind_addr` en [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml) o usa Docker.
### 3. Establecer como DNS del sistema
| Plataforma | Instalar | Desinstalar |
|----------|---------|-----------|
| macOS | `sudo numa install` | `sudo numa uninstall` |
| Linux | `sudo numa install` | `sudo numa uninstall` |
| Windows | `numa install` (admin) + reboot | `numa uninstall` (admin) + reboot |
`install` registra un servicio, apunta el DNS del sistema a Numa y confía en su CA local. `uninstall` revierte las tres cosas. Una vez instalado, el dashboard también está en **http://numa.numa**.
En macOS y Linux, numa se ejecuta como servicio del sistema (launchd/systemd). La unidad systemd no tiene privilegios (`DynamicUser=yes`, solo `CAP_NET_BIND_SERVICE`); el daemon launchd se ejecuta como root. `numa install` reconfigura systemd-resolved mediante un drop-in que `numa uninstall` elimina; cualquier otro proceso que ocupe el puerto 53 (dnsmasq, incluido el de NetworkManager) tiene que detenerse a mano. En Windows, numa se inicia automáticamente al iniciar sesión vía registro. Windows también vincula `127.0.0.2:53` (el Dnscache integrado posee `127.0.0.1:53`) e instala una regla NRPT para enrutar consultas hacia allí — así que edita `bind_addr`/`api_bind_addr` contra `127.0.0.2`, no `127.0.0.1`.
### Iniciar sesión
Sobre loopback (`localhost`, `127.0.0.1`, `numa.numa`) no se necesita inicio de sesión. Cualquier otra cosa, incluido el mapeo de puertos de Docker o la dirección LAN de esta máquina, pide el token de API, que Numa genera en el primer arranque. Imprímelo con `sudo numa token` (una shell de administrador en Windows) e inicia sesión con cualquier nombre de usuario. Fija el tuyo con `[server] api_token` o `NUMA_API_TOKEN`.
### Eliminar todo rastro
`uninstall` restaura el DNS pero conserva el directorio de datos, así que una reinstalación mantiene el mismo token y CA. Para eliminar todo, desinstala primero, luego borra:
| Plataforma | Lo que queda |
|----------|-------------|
| macOS | el binario, `/usr/local/var/numa`, `/usr/local/var/log/numa.log` |
| Linux | el binario (`/usr/local/bin/numa` de install.sh), `/var/lib/numa`, `/etc/numa` |
| Windows | el binario, `%PROGRAMDATA%\numa` |
Las instalaciones por gestor de paquetes eliminan el binario con `brew uninstall`, `pacman -R` o `cargo uninstall`. En Linux, `numa install` copia un binario que no puede ejecutar desde su ubicación original (p. ej. `~/.cargo/bin`) a `/usr/local/bin/numa`, y la desinstalación deja esa copia.
También elimina `~/.config/numa` si creaste una configuración de usuario, y la ruta `[server] data_dir` si configuraste una.
## Servicios Locales
Nombra tus servicios de desarrollo en lugar de recordar números de puerto:
```bash
curl -X POST localhost:5380/services \
-d '{"name":"frontend","target_port":5173}'
```
Ahora `https://frontend.numa` funciona en tu navegador — candado verde, certificado válido, paso de WebSocket para HMR. Sin mkcert, sin nginx, sin `/etc/hosts`.
Añade enrutamiento basado en rutas (`app.numa/api → :5001`), comparte servicios entre máquinas vía descubrimiento LAN, o configura todo en [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml).
## Bloqueo de Anuncios y Privacidad
Bloqueo de anuncios y rastreadores vía [Hagezi Pro](https://github.com/hagezi/dns-blocklists), actualizado diariamente. Funciona en cualquier red — cafeterías, hoteles, aeropuertos. Viaja con tu portátil.
Tres modos de resolución:
- **`forward`** (predeterminado) — proxy transparente a tu DNS de sistema existente. Todo funciona como antes, solo con caché y bloqueo de anuncios encima. Portales cautivos, VPNs, DNS corporativo — todo respetado.
- **`recursive`** — resuelve directamente desde los servidores de nombres raíz. Sin dependencia de upstream, ninguna entidad única ve tu patrón completo de consultas. Añade `[dnssec] enabled = true` para validación completa de cadena de confianza.
- **`auto`** — sondea los servidores raíz al arrancar, recursivo si es alcanzable, si no forward sobre DoH a Quad9 (`https://9.9.9.9/dns-query`), que entonces ve tus consultas. Usa `forward` con tu propio `[upstream]` para elegir un proveedor diferente.
DNSSEC valida la cadena de confianza completa: firmas RRSIG, verificación DNSKEY, delegación DS, pruebas de denegación NSEC/NSEC3. [Lee cómo funciona →](https://numa.rs/blog/posts/dnssec-from-scratch.html)
**Listener DNS-over-TLS** (RFC 7858) — acepta consultas cifradas en el puerto 853 de clientes estrictos como iOS Private DNS, systemd-resolved o stubby. Dos modos:
- **Autofirmado** (predeterminado) — numa genera una CA local automáticamente. `numa install` la añade al almacén de confianza del sistema en macOS, Linux (Debian/Ubuntu, Fedora/RHEL/SUSE, Arch) y Windows, y `numa uninstall` la elimina. En iOS, instala el `.mobileconfig` desde `numa setup-phone`. Firefox mantiene su propio almacén NSS e ignora el del sistema — confía en la CA allí manualmente si necesitas HTTPS para servicios `.numa` en Firefox.
- **Trae tu propio certificado** — apunta `[dot] cert_path` / `key_path` a un certificado de confianza pública (p. ej., Let's Encrypt vía desafío DNS-01 en un dominio que apunte a tu instancia de numa). Los clientes se conectan sin ninguna configuración de almacén de confianza — misma experiencia que AdGuard Home o Cloudflare `1.1.1.1`.
ALPN `"dot"` se anuncia y se aplica en ambos modos; un handshake con ALPN no coincidente se rechaza como defensa contra confusión entre protocolos.
**Oblivious DoH** (RFC 9230) — con `[upstream] mode = "odoh"` ([receta](https://github.com/razvandimescu/numa/blob/main/recipes/odoh-upstream.md)) cada consulta se sella con HPKE hacia el objetivo y se envía a través de un relay. El relay ve tu IP y el texto cifrado, el objetivo ve la pregunta y la IP del relay, y un relay que redirige la consulta entrega al nuevo destino algo que no puede descifrar. Numa rechaza un relay y un objetivo que comparten un dominio registrable. ODoH no oculta la conexión que abres después: tu ISP todavía ve la IP de destino y, sin ECH, el nombre de host en el handshake TLS.
**Configuración del teléfono** — apunta tu iPhone o Android a Numa en un paso:
```bash
numa setup-phone
```
Imprime un código QR. Escanéalo, instala el perfil, activa la confianza del certificado — el DNS de tu teléfono ahora se enruta a través de Numa sobre TLS. Requiere `[mobile] enabled = true` en `numa.toml`.
## Descubrimiento LAN
Ejecuta Numa en múltiples máquinas. Se encuentran automáticamente vía mDNS:
```
Machine A (192.168.1.5) Machine B (192.168.1.20)
┌──────────────────────┐ ┌──────────────────────┐
│ Numa │ mDNS │ Numa │
│ - api (port 8000) │◄───────────►│ - grafana (3000) │
│ - frontend (5173) │ discovery │ │
└──────────────────────┘ └──────────────────────┘
```
Desde la Máquina B: `curl http://api.numa` → proxied to Machine A's port 8000. Actívalo con `numa lan on`.
**Modo hub**: ejecuta una instancia con `bind_addr = "0.0.0.0:53"` y apunta el DNS de otros dispositivos hacia ella — obtienen bloqueo de anuncios + resolución `.numa` sin instalar nada. `bind_addr` también acepta una lista para vincular un subconjunto específico de interfaces. Paso a paso: [receta para toda la red](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
```
Dashboard en `http://localhost:5380`. Con mapeo de puertos o desde otro dispositivo, inicia sesión con cualquier nombre de usuario y el token de `docker exec numa numa token`. El volumen `numa-data` conserva el token y la CA local entre recreaciones del contenedor. La imagen vincula la API y el proxy a `0.0.0.0` por defecto. Sobrescribe con una configuración personalizada:
```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
```
Multi-arquitectura: `linux/amd64` y `linux/arm64`.
Recetas compose listas para usar:
- [`packaging/client/`](https://github.com/razvandimescu/numa/blob/main/packaging/client) — modo cliente ODoH (DNS anónimo), Numa + `numa.toml` inicial.
- [`packaging/relay/`](https://github.com/razvandimescu/numa/blob/main/packaging/relay) — relay ODoH público, Numa + Caddy + ACME.
## Cómo se Compara
| | Pi-hole | AdGuard Home | Unbound | Numa |
|---|---|---|---|---|
| Proxy de servicios locales + TLS automático | — | — | — | dominios `.numa`, HTTPS, WebSocket |
| Descubrimiento de servicios LAN | — | — | — | mDNS, cero configuración |
| Sobrescrituras de desarrollador (REST API) | — | — | — | Reversión automática, scriptable |
| Resolutor recursivo | — | — | Sí | Sí, con selección SRTT |
| Validación DNSSEC | — | — | Sí | Sí (RSA, ECDSA, Ed25519) |
| Bloqueo de anuncios | Sí | Sí | — | Hagezi Pro |
| Reglas por cliente | Grupos | Sí | Vistas / etiquetas | Por CIDR (`[[client_policy]]`), solo archivo de configuración |
| UI de administración web | Completa | Completa | — | Dashboard |
| Upstream cifrado (DoH/DoT) | Necesita cloudflared | Solo DoH | Solo DoT | DoH + DoT (`tls://`) |
| Clientes cifrados (listener DoT) | Necesita sidecar stunnel | Sí | Sí | Nativo (RFC 7858) |
| Endpoint servidor DoH | — | Sí | — | Sí (RFC 8484) |
| Request hedging | — | — | — | Todos los protocolos (UDP, DoH, DoT) |
| Serve-stale + prefetch | — | — | Prefetch al 90% del TTL | RFC 8767, prefetch al 90% del TTL |
| Reenvío condicional | — | Sí | Sí | Sí (reglas por sufijo) |
| Portátil (laptop) | No (appliance) | No (appliance) | Servidor | Binario único, macOS/Linux/Windows |
| Madurez de la comunidad | 56K estrellas, 10 años | 33K estrellas | 20 años | Nuevo |
## Ejecutar Numa como tu DNS Principal
**Si Numa se detiene.** `numa install` registra Numa con launchd (macOS) o systemd (Linux), que lo reinician cuando sale. Si Numa es tu único resolutor, las búsquedas DNS fallan hasta que se reinicie. Para dejar de usar Numa y restaurar la configuración DNS previa de la máquina, ejecuta `sudo numa uninstall`.
Si Numa está en ejecución pero los upstreams no son alcanzables, puede servir respuestas en caché hasta una hora después de su TTL (RFC 8767). Los nombres que no ha cacheado fallan.
**Pruebas.** El parser DNS se somete a fuzzing en cada pull request que lo toca, con ejecuciones más largas semanalmente ([`fuzz.yml`](https://github.com/razvandimescu/numa/blob/main/.github/workflows/fuzz.yml)). CI ejecuta `cargo audit` sobre las dependencias e instala, reinstala y desinstala Numa en macOS y Linux.
**Endurecimiento del resolutor.** En modo recursivo, Numa descarta registros de respuesta fuera de la zona consultada, se niega a consultar servidores de nombres en direcciones privadas o loopback, y limita las consultas upstream y la profundidad de referencias de cada búsqueda. Las respuestas sobre UDP plano deben coincidir con el ID de transacción aleatorio y la pregunta de la consulta. Las consultas ANY se rechazan. La validación DNSSEC está desactivada por defecto; `numa dnssec on` la activa. Para reportar una vulnerabilidad de forma privada, consulta [SECURITY.md](https://github.com/razvandimescu/numa/blob/main/SECURITY.md).
**Lo que Numa no hace.** Sin DHCP, sin clustering, sin sincronización de configuración entre instancias. La mayoría de los ajustes viven en [`numa.toml`](https://github.com/razvandimescu/numa/blob/main/numa.toml), no en el dashboard. Para toda una red, ejecútalo en una máquina que permanezca encendida.
## Rendimiento
Consultas en caché de 0.1ms — iguala a Unbound y AdGuard Home. La caché a nivel de red almacena bytes crudos con parcheo de TTL in-place. Request hedging elimina los picos p99: p99 recursivo en frío 538ms vs Unbound 748ms (−28%), σ 4× más ajustada. [Benchmarks →](https://github.com/razvandimescu/numa/blob/main/benches)
## Aprende Más
- [Blog: Numa como tu resolutor de tailnet](https://numa.rs/blog/posts/numa-tailnet-resolver.html)
- [Blog: DNS-over-TLS desde Cero en Rust](https://numa.rs/blog/posts/dot-from-scratch.html)
- [Blog: Implementando DNSSEC desde Cero en Rust](https://numa.rs/blog/posts/dnssec-from-scratch.html)
- [Blog: Construí un Resolutor DNS desde Cero](https://numa.rs/blog/posts/dns-from-scratch.html)
- [Referencia de configuración](https://github.com/razvandimescu/numa/blob/main/numa.toml) — todas las opciones documentadas en línea; `numa config path` muestra qué archivo está usando tu instalación, `numa config edit` lo abre
- [REST API](https://github.com/razvandimescu/numa/blob/main/src/api.rs) — sobrescrituras, caché, bloqueo, servicios, diagnósticos
- [numa-metrics](https://github.com/razvandimescu/numa-metrics) — historial de consultas y analíticas duraderas, fuera del host por diseño (sin escrituras en tarjeta SD)
## Hoja de Ruta
- [x] Reenvío DNS, caché, bloqueo de anuncios, sobrescrituras de desarrollador
- [x] Dominios locales `.numa` — TLS automático, enrutamiento por rutas, proxy WebSocket
- [x] Descubrimiento de servicios LAN — mDNS, DNS + proxy entre máquinas
- [x] DNS-over-HTTPS — upstream cifrado + endpoint servidor (RFC 8484)
- [x] DNS-over-TLS — listener de clientes cifrado (RFC 7858) + reenvío upstream (`tls://`)
- [x] Oblivious DoH — modo cliente anonimizado + relay público (`numa relay`, RFC 9230)
- [x] Resolución recursiva + DNSSEC — cadena de confianza, NSEC/NSEC3
- [x] Selección de servidor de nombres basada en SRTT
- [x] Failover multi-forwarder — múltiples upstreams con ranking SRTT, pool de respaldo
- [x] Request hedging — solicitudes paralelas rescatan pérdida de paquetes y latencia de cola (todos los protocolos)
- [x] Serve-stale + prefetch — RFC 8767, refresco en segundo plano a <10% del TTL y al servir stale
- [x] Reenvío condicional — reglas por sufijo para DNS split-horizon (Tailscale, VPNs)
- [x] Calentamiento de caché — resolución proactiva para dominios configurados
- [x] Incorporación móvil — flujo QR `setup-phone`, API móvil, perfiles mobileconfig
- [ ] Integración pkarr — DNS autosoberano vía Mainline DHT
- [ ] Nombres `.numa` globales — respaldados por DHT, sin registrador
## Licencia
MIT