Hay un resolver de DNS corriendo en tu máquina ahora mismo. No lo instalaste, probablemente nunca lo has configurado, y está respondiendo a cada consulta de nombre que hace la caja. En Fedora, Ubuntu y la mayoría del Linux de escritorio es systemd-resolved, y lleva ahí sentado en silencio desde el día en que se instaló el sistema.

Puede hacer tres cosas que merece la pena tener. Cachea, así que la misma consulta no cruza la red dos veces. Valida DNSSEC, así que una respuesta forjada se rechaza en vez de creerse. Y habla DNS sobre TLS, así que la red local no puede leer cada nombre que preguntas.

Por defecto hace exactamente una de ellas. Las otras dos vienen apagadas, y en algunas distribuciones están apagadas en tiempo de compilación, lo que significa que el ajuste que cambiarías ni siquiera es el ajuste que lo decidió. Un validador que nunca valida no sirve ni de adorno.

Esto es lo que la cosa hace de verdad, medido en una máquina Fedora 44 en marcha con systemd 259, y lo que cambia cuando activas las otras dos. Incluido lo que tu distribución decidió por ti en tiempo de compilación, y cuánto de eso puedes sencillamente revocar.

Qué está respondiendo de verdad a tus consultas

Empecemos por el retrato honesto, porque «usa /etc/resolv.conf» lleva años sin ser cierto en estos sistemas.

systemd-resolved se expone de cuatro maneras distintas, y cuál use un programa decide lo que le llega de vuelta.1 El camino de glibc es nss-resolve, conectado a través de /etc/nsswitch.conf. Los caminos nativos son D-Bus y Varlink, que llevan el veredicto DNSSEC y el ámbito de interfaz que getaddrinfo no tiene forma de expresar. Luego el stub a la escucha, un servidor DNS de verdad en el loopback para cualquier cosa que hable DNS crudo y no sepa nada de lo anterior.

Cuatro puertas, un solo demonio.

En esta máquina ese cableado tiene esta pinta:

$ ls -l /etc/resolv.conf
lrwxrwxrwx. 1 root root 39 May 30 13:56 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

$ cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
search damiendye.uk

$ grep ^hosts: /etc/nsswitch.conf
hosts:      files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns

resolve en esa línea es nss-resolve. dns detrás es el nss-dns de toda la vida, ahí sentado como respaldo que solo entra si resolved no está corriendo en absoluto.

Cuatro formas de entrar, un demonio, y una decisión de enrutado antes de que nada salga de la cajaCÓMO PREGUNTA UN PROGRAMAQUÉ HACE EL DEMONIOADÓNDE VAnss-resolveglibc, sin veredictoD-Bus, Varlinknativo, veredicto completo127.0.0.53el stub completo127.0.0.54el stub proxysystemd-resolved1. hosts y sintéticos2. caché3. validador DNSSEC4. enrutado5. transporteel stub proxy se salta 2 y 3LLMNR en 5355DNS de arriba
Cuatro formas de entrar, un demonio, y una decisión de enrutado antes de que nada salga de la caja

Hay dos stubs a la escucha, no uno

Todo el mundo conoce 127.0.0.53. Menos gente sabe que hay un segundo.

$ ss -lntup | grep ':53 '
udp   UNCONN 0  0     127.0.0.54:53   0.0.0.0:*
udp   UNCONN 0  0  127.0.0.53%lo:53   0.0.0.0:*
tcp   LISTEN 0  4096  127.0.0.54:53   0.0.0.0:*
tcp   LISTEN 0  4096 127.0.0.53%lo:53 0.0.0.0:*

No son dos direcciones para lo mismo. El segundo hace menos a propósito:2

127.0.0.53127.0.0.54
PapelEl resolver local completoSolo modo proxy
CachéSíNo
Validación DNSSECSí, cuando está activadaNunca
LLMNR y Multicast DNSSíNo
Nombres sintéticos (localhost, _gateway)SíNo
Sube a DNS sobre TLSSíSí
Nombre sintético propio_localdnsstub_localdnsproxy
Lo que recibesLo que decidió resolvedLo que dijo el servidor de arriba

La documentación es rotunda sobre la segunda columna. Va a «pass most DNS messages relatively unmodified to the current upstream DNS servers and back, but not try to process the messages locally, and hence does not validate DNSSEC, or offer up LLMNR/MulticastDNS».2

Eso convierte a 127.0.0.54 en el destino correcto para un programa que quiere hacer su propia validación, o para uno que necesita la respuesta cruda de arriba en vez de la interpretación que resolved hace de ella.

Ambas direcciones tienen nombres sintéticos, _localdnsstub y _localdnsproxy, que resuelven sin configuración ninguna.

Cuatro modos para /etc/resolv.conf, no tres

El modo se detecta automáticamente a partir de lo que es el fichero, y hay cuatro.1

/etc/resolv.conf esQué significaClientes que saltan NSS
enlace simbólico a run/systemd/resolve/stub-resolv.confEl modo recomendado. Lista 127.0.0.53 y los dominios de búsqueda vivosPasan por resolved
enlace simbólico a /usr/lib/systemd/resolv.confEstático, lista 127.0.0.53, no lleva dominios de búsquedaPasan por resolved
enlace simbólico a run/systemd/resolve/resolv.confLista los servidores reales de arriba, mantenidos al díaSe saltan resolved por completo
un fichero real gestionado por otra cosaresolved lo lee como consumidor, no como proveedorSe saltan resolved por completo

Esa tercera fila pilla a la gente. Parece la opción ordenada, se mantiene al día, y significa calladamente que cada programa que lee resolv.conf directamente está hablando con el resolver de tu ISP sin caché, sin validación y sin cifrado, tengas lo que tengas configurado en resolved.conf.

La opción trust-ad del fichero de arriba también importa. Sin ella, glibc arranca el bit AD de la respuesta antes de que tu programa la vea, con el razonable argumento de que la pretensión de un resolver cualquiera de haber validado algo no vale nada. Con 127.0.0.53 como servidor de nombres la pretensión viene de tu propia máquina, así que trust-ad es correcto aquí y resolved te lo escribe él.

Sobre las teorías de la conspiración de systemd

Antes de cualquier detalle, porque este es el tema donde siempre aparece.

Si tu objeción a lo que viene es una teoría sobre Lennart Poettering en persona, o sobre los motivos de Red Hat, o sobre que systemd es un complot para quitarte algo, ya puedes ir largándote, porque no tengo el menor interés en escuchar tu ficción.

Todo lo de esta entrada salió de una máquina viva: el código fuente, los flags de compilación, el binario que se distribuye, las páginas de manual y el comportamiento medido. Cada afirmación tiene al lado un comando que puedes ejecutar tú mismo y comprobar. Los flags de compilación son públicos. El código es público. El único valor por defecto que creo que está genuinamente mal lo eligió gente que escribió su razonamiento donde cualquiera puede leerlo y discutírselo, que es exactamente lo que hago más abajo.

Eso es bastante más transparencia de la que te da la mayoría del software que ejecutas sin una palabra de queja.

Trae pruebas o déjalo estar.

La caché es la parte que simplemente funciona

Esta es la única función que viene activada por defecto en todas partes, y es la que se gana el sueldo sin configuración ninguna.

La medición, en esta caja, sobre 32 dominios. Vaciar la caché, consultarlos todos, consultarlos otra vez, y leer el tiempo de consulta que reporta dig en vez de cronometrar el proceso:

$ resolvectl flush-caches
$ for n in $NAMES; do dig +tries=1 @127.0.0.53 "$n" A | grep 'Query time'; done
medianamediap90máxtotal de los 32 nombres
caché fría103,5 ms106,7 ms184 ms238 ms3 414 ms
caché caliente0,0 ms0,5 ms1 ms3 ms16 ms

3 414 milisegundos frente a 16. Ese es el argumento entero para una caché local, y es por lo que esto viene por defecto.

Vale la pena ser honesto sobre qué es ese número, eso sí. Es el ahorro en una tanda de treinta y dos nombres que nunca se habían consultado, frente a la misma tanda repetida, y ninguna carga real se parece a ninguna de las dos. La cifra útil es la cola, no la mediana: la peor consulta de ese conjunto costó 238 ms en frío y 3 ms en caliente. Una página que arrastra ocho nombres de host no se preocupa por tu mediana, espera al más lento.

Los diales

[Resolve]
Cache=yes                 # yes | no-negative | no
CacheFromLocalhost=no     # default: do not cache answers from 127.0.0.1
StaleRetentionSec=0       # serve expired records when upstream is down
AjustePor defectoQué hace
Cache=yesactivoCachea respuestas positivas y negativas
Cache=no-negativeSolo respuestas positivas, para cuando estás harto de esperar a que expire un TTL negativo
CacheFromLocalhost=noactivoNo cachea nada cuando el servidor de arriba está en 127.0.0.1
StaleRetentionSec=0inactivoSirve registros caducados cuando el de arriba deja de responder
DNSCacheSize=40964096Registros guardados por ámbito. Solo systemd 261 en adelante

Esa tercera fila es la que muerde. Si tu servidor de arriba es un dnsmasq o un unbound en el loopback, resolved no va a cachear sus respuestas en absoluto, con el argumento de que aquello con lo que habla ya es una caché. Apunta resolved a un resolver con filtrado en otro host y tienes dos capas de caché. Apúntalo a uno en el loopback y tienes una.

StaleRetentionSec= es el interesante, y viene desactivado. Actívalo y, cuando el de arriba deje de responder, resolved sigue sirviendo registros pasado su TTL en vez de fallar.2 Siempre prueba primero con el de arriba. No se aplica a NXDOMAIN, porque un nombre que no existe es una respuesta perfectamente válida y no tiene nada de rancia. Para un portátil que entra y sale de cobertura, o una máquina que tiene que seguir funcionando durante una caída de DNS, esto merece la pena:

[Resolve]
StaleRetentionSec=1d

systemd 261 añadió encima de esto el dimensionado de caché por protocolo, con DNSCacheSize=, MulticastDNSCacheSize= y LLMNRCacheSize=, cada uno con 4096 registros por defecto y un tope de 2^24.3 No en esta caja, que corre la 259, ni en ninguna versión estable actual de ninguna distribución. Vale la pena saber que viene, porque hasta ahora el tamaño de la caché no era ajustable en absoluto.

El demonio además vacía todo cuando hay presión de memoria, lo cual es sensato y ocasionalmente sorprendente cuando intentas averiguar por qué una tasa de aciertos de caché pinta mal.

El split DNS es la razón para quedárselo

Si te llevas una sola cosa de esto, llévate esta sección, porque es la función que genuinamente hace algo que el viejo resolver stub no podía.

El resolv.conf de toda la vida tiene una lista de servidores de nombres para la máquina entera. Una lista. Levanta una VPN y algo tiene que sobrescribirla, lo que significa o bien que tus nombres internos funcionan y el resto del DNS pasa por el resolver corporativo, o al revés. No hay tercera opción. El fichero no puede expresarla.

resolved enruta por consulta y por interfaz.1 Cada enlace tiene sus propios servidores y sus propios dominios, y una consulta se manda al enlace cuyo dominio encaja mejor con el nombre, por número de etiquetas.

ConfiguraciónSe escribeEfecto
Dominio de búsquedadamiendye.ukSufijo para nombres de una sola etiqueta, y enruta a este enlace las consultas que encajen
Dominio solo de ruta~internal.exampleEnruta a este enlace las consultas que encajen, nunca se usa como sufijo
Ruta comodín~.Manda a este enlace todo lo que no encaje en otro sitio
Ruta por defectoDNSDefaultRoute=yesRecoge las consultas sin encaje, sin reclamar ~.

Así que un portátil con una VPN de trabajo levantada tiene esto:

resolvectl domain tun0 '~corp.example' '~10.in-addr.arpa'
resolvectl dns    tun0 10.0.0.53

Los nombres bajo corp.example y las consultas inversas de 10.0.0.0/8 bajan por el túnel. Todo lo demás sigue saliendo por el enlace local como antes. A nadie le reescribieron su resolv.conf, y cuando el túnel se cae las rutas se van con él.

Una consulta, dos enlaces, y una decisión de enrutado tomada por número de etiquetasLA CONSULTALA DECISIÓNEL ENLACEdb01.corp.example14.0.10.in-addr.arpablogs.damiendye.ukGana quien tiene más etiquetasCada enlace tiene sus propiosservidores y dominios.tun0~corp.examplewlp4s0DefaultRouteCon ~ solo enruta. Sin ~, además sufija los nombres de una sola etiqueta.
Una consulta, dos enlaces, y una decisión de enrutado tomada por número de etiquetas

Vale la pena enunciar una regla con claridad, porque es la que la gente busca y entiende al revés. ~. en un enlace significa prefiere este enlace para todo. También impide implícitamente que ningún otro enlace sea ruta por defecto. ¿Quieres que un enlace recoja las sobras sin reclamar todo el espacio de nombres? Pon DNSDefaultRoute=yes y deja ~. en paz.

Comprueba qué decidió en vez de suponerlo:

$ resolvectl status
Link 3 (wlp4s0)
    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
         Protocols: +DefaultRoute LLMNR=resolve -mDNS -DNSOverTLS
                    DNSSEC=no/unsupported
Current DNS Server: 192.0.2.53
       DNS Servers: 192.0.2.53 2001:db8:1::53
        DNS Domain: damiendye.uk
     Default Route: yes

Cada dirección de esta entrada es de los rangos de documentación, 192.0.2.0/24 y 2001:db8::/32, así que no las pegues en una configuración esperando respuesta.4 5 Las cifras y el comportamiento son reales, sacados de una máquina viva. Las direcciones son sustitutas, porque una dirección IPv6 global construida de la forma habitual lleva la MAC de la interfaz en su mitad baja, y publicar una reparte de paso un trozo de inventario de hardware.

Ese -DNSOverTLS y ese DNSSEC=no son las dos secciones siguientes.

Puede validar DNSSEC

Upstream describe el demonio como «a caching and validating DNS/DNSSEC stub resolver».1 La mitad validadora es real, no es un envoltorio alrededor de otra cosa, y funciona. Simplemente no está encendida.

Encenderla por enlace no necesita sudo, porque resolvectl pasa por polkit y una sesión local activa tiene permiso:

$ resolvectl dnssec wlp4s0 yes
$ resolvectl status wlp4s0 | grep DNSSEC
                    DNSSEC=yes/supported

Ese sufijo /supported es resolved reportando lo que encontró cuando sondeó al servidor de arriba, aparte de lo que tú pediste. yes/supported significa que pediste validación y el servidor puede llevarla. no/unsupported en una instalación por defecto significa que nadie la pidió, así que nadie sondeó.

Una vez encendida, cada consulta vuelve con un veredicto, y hay tres.

Seguro, inseguro y espurio son tres respuestas distintas¿PUBLICA EL PADRE UN DS, Y VERIFICAN LAS FIRMAS?SEGUROdamiendye.ukFirmado, y la cadenacuadra.Devuelve datos.INSEGUROsystemd.ioSin DS. Sin firmar,no hay nada que comprobar.Devuelve datos.ESPURIOdnssec-failed.orgAfirma estar firmado.La prueba falla.Consulta rechazada.Dos de las tres devuelven datos. Activar la validación no impide que funcionen los sitios sin firmar.
Seguro, inseguro y espurio son tres respuestas distintas, y solo una de ellas es un fallo

Aquí están las tres en esta máquina, contra nombres reales:

$ resolvectl query damiendye.uk
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: no

$ resolvectl query systemd.io
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no

$ resolvectl query dnssec-failed.org
dnssec-failed.org: resolve call failed: DNSSEC validation failed: missing-key
  (DNSKEY Missing: no SEP matching the DS found for dnssec-failed.org.)

damiendye.uk está firmado, así que la cadena desde la raíz valida y el dato está autenticado. systemd.io no está firmado, así que no hay nada que comprobar y resolved lo dice honestamente en vez de fingir. Ese es el dominio propio del proyecto systemd, un punto al que voy a volver. dnssec-failed.org se publica con firmas rotas a propósito. Ese falla en seco, con un diagnóstico que nombra el problema exacto.

Aquí está la distinción que se pierde. Inseguro no es un fallo. Una zona sin firmar devuelve datos y te dice que no se pudieron verificar. Solo se rechaza una zona que afirma estar firmada y luego no puede demostrarlo.

La cadena en sí es visible si quieres verla:

$ dig +short @127.0.0.53 uk DS
43876 8 2 A107ED2AC1BD14D924173BC7E827A1...

$ dig +short @127.0.0.53 damiendye.uk DS
2371 13 2 A5B2825C57899A5A15EE9703832C8358E0D19EF29DC72DD83C691ED77C33BD7F

$ dig +short @127.0.0.53 damiendye.uk DNSKEY
256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWz...
257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d...

La raíz avala a uk, uk avala a damiendye.uk, y la clave 257 firma la clave 256 que firma los registros. El algoritmo 13 de ahí es ECDSA P-256, que es lo que quieres en una zona nueva en vez del RSA que el DS de uk de arriba sigue usando.

A través del stub simple, un programa que jamás oyó hablar de resolved recibe el mismo veredicto en forma de flag AD:

$ dig @127.0.0.53 ietf.org A | grep flags
;; flags: qr rd ra ad;

$ dig @127.0.0.53 dnssec-failed.org A | grep status
;; ->>HEADER<<- status: SERVFAIL

Y el demonio lleva un recuento en marcha, que es la forma más rápida de ver si la validación está haciendo algo:

$ resolvectl statistics
DNSSEC Verdicts
                    Secure:  134
                  Insecure:   62
                     Bogus:    0
             Indeterminate:    2

Lo que cuesta validar

Aquí voy a decepcionarte, porque intenté medir esto como es debido y no pude.

En caliente, está limpio y no es nada. Los mismos 32 nombres contra una caché llena costaron 16 ms sin validación y 23 ms con ella. Llámalo error de redondeo.

En frío es donde debería notarse el coste, y un solo cliente no puede aislarlo. Hice el barrido en ambos sentidos y obtuve respuestas contradictorias: en un orden la validación parecía un 74 % más lenta, en el otro parecía más rápida, lo cual es imposible y te dice qué se está midiendo en realidad. El barrido que corra segundo se beneficia de que el resolver de arriba ya haya traído todo lo que pidió el primero. La variable que domina no es la validación, es de quién era la caché que estaba caliente.

Así que no te voy a dar un número que no puedo sostener. Lo que sí es cierto es el mecanismo, y la documentación enuncia su forma con claridad: la validación «requires retrieval of additional DNS data, and thus results in a small DNS lookup time penalty».2 Una consulta validada en frío recorre la cadena de delegación trayendo DS y DNSKEY en cada nivel antes de poder responder, así que cuesta viajes de ida y vuelta extra en nombres que nadie ha pedido todavía, y no cuesta nada en nombres que alguien ya ha pedido.

Que es también por lo que la misma página avisa de que apagar la caché «comes at a performance penalty, which is particularly high when DNSSEC is used».2 Las dos funciones no son independientes. La validación es asumible precisamente porque la caché hace que la pagues una vez.

Si quieres la cifra real para tu propia red, mídela ahí. Un cliente en una conexión no puede decírtelo.

Entonces, ¿por qué está la validación apagada?

Porque tu distribución la apagó cuando compiló el paquete, y lo hizo por una razón que dejó escrita.

El systemd de upstream trae la validación activada. La opción de compilación lo dice:

option('default-dnssec', type : 'combo',
       choices : ['yes', 'allow-downgrade', 'no'],
       value : 'allow-downgrade')

y el manual de upstream coincide: DNSSEC= «Defaults to allow-downgrade».6

Ahora mira lo que Fedora le pasa a esa compilación:7

-Ddefault-dnssec=no
-Ddefault-dns-over-tls=no
-Ddefault-mdns=no
-Ddefault-llmnr=resolve

Y lo que pasan Debian y Ubuntu:8

-Ddefault-dnssec=no
-Ddefault-llmnr=no
-Ddefault-mdns=no
-Ddns-over-tls=openssl

Así que el fichero de /usr/lib/systemd/resolved.conf en una caja Fedora, el que va encabezado con «Entries in this file show the compile time defaults», pone #DNSSEC=no. Léelo otra vez, porque está haciendo algo taimado: la línea parece el software contándote su propio valor por defecto, y lo que en realidad te está devolviendo impreso es el flag de compilación de Fedora, con el valor por defecto real de upstream en ninguna parte de la página.

AjustePor defecto en upstreamCompilación de FedoraCompilación de Debian/UbuntuResultado en esta caja
DNSSEC=allow-downgradenonoDNSSEC=no
DNSOverTLS=nonono (compilado con OpenSSL)-DNSOverTLS
MulticastDNS=yesnono-mDNS
LLMNR=yesresolvenoLLMNR=resolve

Cada uno de esos encaja con lo que resolvectl status imprime en esta máquina. Código fuente, flag de compilación y sistema en marcha, los tres coinciden.

El razonamiento de Fedora está en la propuesta de cambio y es refrescantemente directo. Se sabe que la función «is known to cause compatibility problems with certain network access points», y Fedora «is not prepared to handle an influx of DNSSEC-related bug reports», así que se va fuera.9

¿Puedo preguntar por qué la respuesta a una función que se rompe en redes malas es desactivarla para todo el mundo, en vez de traer allow-downgrade como hace upstream y dejar que se apague sola donde tenga que hacerlo? No quién lo decidió. Qué hubo en el proceso que llevó ahí. Porque allow-downgrade existe precisamente para el caso del portal cautivo, es el valor por defecto de upstream justo por esa razón, y traer no en su lugar significa que una máquina en una red perfectamente buena tampoco obtiene validación.

Para ser justos con ellos, allow-downgrade tiene su propio problema, y es real. El modo detecta un resolver que no puede hacer DNSSEC y deja de validar calladamente. Un atacante que pueda dar forma a tus respuestas DNS puede hacer que esa detección salte a propósito, y la documentación lo dice sin rodeos: «makes DNSSEC validation vulnerable to ‘downgrade’ attacks».2 Una función de seguridad que cualquier atacante puede apagar hace menos de lo que parece.

Así que ninguno de los dos valores por defecto es bueno. no no te da nada. allow-downgrade te da algo que un atacante puede quitarte, y yes te da lo de verdad más todo lo de la siguiente sección.

Cuánto de la web está firmado, en realidad

Antes de gastar mucho esfuerzo en la validación vale la pena saber qué fracción de tus consultas puede proteger siquiera. Así que le pedí a resolved el veredicto sobre el ápice de treinta y dos dominios que este asunto implica de verdad: los organismos que escribieron el estándar, las casas que distribuyen el resolver, y la infraestructura que todo el mundo resuelve quiera o no.

Dieciséis de treinta y dos. La mitad.

FirmadoSin firmar
Estándares y registrosietf.org, iana.org, icann.org, rfc-editor.org, ripe.net, isc.org, nlnetlabs.nl, nic.cz, afnic.fr, verisign.comninguno
Distribuciones y fabricantesdebian.org, fedoraproject.org, opensuse.org, almalinux.orgredhat.com, ubuntu.com, canonical.com, suse.com, rockylinux.org, archlinux.org
systemd mismoningunosystemd.io, freedesktop.org
Infraestructuracloudflare.com, gov.ukgithub.com, kernel.org, google.com, wikipedia.org, mozilla.org, apache.org, gnu.org, quad9.net

Lee la primera columna de arriba abajo. Todos y cada uno de los organismos de estándares y registros han firmado. Diez de diez, sin excepciones: la gente que escribió DNSSEC, y la gente que lleva los registros que publican los registros DS de todos los demás. Hicieron el trabajo en sus propias zonas.

Ahora lee la tercera fila de izquierda a derecha. systemd.io no tiene DS. El proyecto que escribió el resolver validador del que trata esta entrada entera no ha firmado su propio dominio, ni tampoco freedesktop.org, donde vive su documentación. Debajo, quad9.net tampoco está firmado, lo que merece un segundo de reflexión: un resolver público cuyo argumento de venta entero es que valida DNSSEC por ti, sobre una zona que nadie puede validar.

Y los fabricantes se parten limpiamente por una línea. Las distribuciones comunitarias firmaron. debian.org, fedoraproject.org, opensuse.org, almalinux.org. Las empresas no. redhat.com, ubuntu.com, canonical.com, suse.com. Esas son las cuatro organizaciones que empaquetan y distribuyen este resolver a la mayor parte del parque Linux.

Ahí no hay ninguna laguna tecnológica. El protocolo lleva más de quince años desplegable, las herramientas son gratis, y los registros aceptan el registro DS sin cobrar por él. Te lo digo gratis: la gente que escribió el estándar ha firmado, y la mayoría de la gente que distribuye el software no.

Hay una versión más afilada del mismo argumento en gov.uk, que sí está firmado, y luego hace esto:

$ dig +short @127.0.0.53 www.gov.uk CNAME
www-cdn.production.govuk.service.gov.uk.

$ dig +short @127.0.0.53 service.gov.uk DS
                                          (nothing)

uk tiene un DS. gov.uk tiene un DS. service.gov.uk no tiene ninguno, así que la cadena se para en seco ahí, y www.gov.uk resuelve inseguro aunque el ápice por encima esté firmado como es debido. Alguien hizo el trabajo en gov.uk y luego apuntó la web de verdad a una delegación sin firmar. Un trabajo a medias.

Por eso, si estás firmando una zona, comprueba los nombres que la gente teclea de verdad. Un ápice firmado que hace CNAME hacia una zona de CDN sin firmar no te aporta nada.

DNS sobre TLS funciona, con condiciones

DNS sobre TLS está implementado, está compilado en todas las builds mayoritarias, y funciona. Viene apagado en todas partes, incluido upstream, donde DNSOverTLS= «Defaults to no».6

La configuración son dos líneas, y la segunda es la que la gente se salta:

[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes

Ese #dns.quad9.net no es decoración. Fija el nombre usado para SNI y para validar el certificado. Quítalo y el certificado se comprueba «checked against the server’s IP».2 Eso funciona con los grandes proveedores porque meten direcciones IP en los SAN de su certificado, pero es la comprobación más débil, se rompe en cuanto un proveedor deja de hacerlo, y no te da ninguna protección contra que te redirijan a otra dirección que resulta tener un certificado válido para sí misma. El propio artículo de Fedora Magazine sobre esto omite el nombre de host,10 lo cual es una pena, porque la sintaxis está ahí mismo en los comentarios del fichero de configuración que se distribuye.

Estricto y oportunista son ajustes muy distintos

ModoEn un servidor que soporta DoTEn un servidor que noAutentica al servidor
yesCifradoFallan todas las consultasSí
opportunisticCifradoTexto plano en silencioNo
noTexto planoTexto planon/a

opportunistic suena al término medio sensato y en general no lo es. La documentación lo dice a las claras: en ese modo «the resolver is not capable of authenticating the server, so it is vulnerable to ‘man-in-the-middle’ attacks»,2 y cualquiera que pueda tirar tu tráfico del puerto 853 puede forzar la degradación. Te protege de la observación pasiva en una red donde nadie lo está intentando. Contra alguien que sí lo intenta, no hace nada.

yes es el ajuste honesto. También significa que cuando no puede conectar te quedas sin DNS ninguno, lo cual conviene saber antes de ponerlo en una máquina a la que no puedes acercarte andando.

Probé DoT estricto contra Quad9 desde esta caja y la consulta se colgó. Sin error, sin timeout, nada en el journal entre el vaciado y mi marcha atrás dos minutos después:

Sep 27 17:12:11 systemd-resolved[40105]: wlp4s0: Bus client set DNS server list to: 9.9.9.9#dns.quad9.net, ...
Sep 27 17:12:14 systemd-resolved[40105]: wlp4s0: Bus client set DNSOverTLS setting: yes
Sep 27 17:12:15 systemd-resolved[40105]: Flushed all caches.
Sep 27 17:14:39 systemd-resolved[40105]: wlp4s0: Bus client set DNS server list to: 192.0.2.53, ...

No puedo decirte desde aquí si el puerto 853 es alcanzable en esa conexión, porque la shell desde la que hice la prueba tampoco alcanzaba el puerto 443 y estaba claramente filtrada ella misma. Lo que el log sí muestra es el modo de fallo: el DoT estricto que no puede conectar no reporta nada útil. Espera. Si activas esto y tu DNS se queda mudo, comprueba ss -tn dport = :853 antes de ir a mirar resolved.

Probarlo como es debido

# does the transport actually come up
resolvectl flush-caches
resolvectl query ietf.org        # expect: acquired via ... encrypted transport: yes

# is anything still going out in the clear
sudo tcpdump -ni any 'port 53 and not host 127.0.0.53'

El segundo comando es el que dice la verdad. Con DoT funcionando, nada debería salir de la máquina por el puerto 53, y cualquier cosa que salga es un programa que ha encontrado la forma de rodear el stub.

DNS sobre HTTPS no existe aquí

Respuesta directa a la pregunta: systemd-resolved no soporta DNS sobre HTTPS. Ni parcialmente, ni detrás de un flag, ni con una opción de compilación que nadie activa. Ahí dentro no hay DoH en absoluto.

Y eso no se lee de la documentación, que podría estar simplemente desfasada. Es lo que contiene el binario:

$ strings /usr/lib/systemd/systemd-resolved | grep -icE 'application/dns-message|dns-query|:443'
0

$ grep -oE 'name="DNSOver[A-Za-z]*"' /usr/share/dbus-1/interfaces/org.freedesktop.resolve1.Manager.xml
name="DNSOverTLS"

Ningún formato de cable DoH, nada de HTTP/2, una sola propiedad de cifrado en la interfaz D-Bus y es TLS. La lista completa de directivas de resolved.conf en upstream llega a diecisiete ajustes y ninguno menciona HTTPS.6

Se ha pedido. La incidencia #8639, «Add support for DNS-over-HTTPS to systemd-resolved», se abrió el 2 de abril de 2018 y sigue abierta, con dos pull requests adjuntas y nada fusionado.11 Ocho años y subiendo.

Misma carga, mismo cifrado, puerto distintoAMBOS LLEVAN LA MISMA CONSULTA DNS, DENTRO DEL MISMO TLSDNS sobre TLSDNS sobre HTTPStcp/853tcp/443En systemd-resolvedsí, desde v239no, en absolutoUn observador ve los nombresnonoUna red puede bloquearlosí, tiene puerto propiono sin dolorPuedes auditar el tuyosínoPedido en la incidencia 8639 de systemd, abril de 2018. Sigue abierta.
Misma carga, mismo cifrado. La diferencia es en qué puerto va, y quién puede saberlo

¿Es una decisión equivocada? No evidentemente, y el argumento a favor de DoT es decente. Ambos llevan DNS dentro de TLS y ambos detienen al mismo observador pasivo. La ventaja de DoH es que se esconde en el puerto 443 con todo lo demás, así que una red que quiera bloquear el DNS cifrado tiene que trabajar mucho más. Eso es genuinamente útil si el operador de la red es el adversario.

Corta también en la otra dirección. Cuando el operador eres tú, esa indistinguibilidad significa que tampoco puedes auditar tu propio DNS, y cada aplicación que trae su propio cliente DoH deja de usar el resolver del sistema, que es como acabas con un navegador que ignora tu split DNS, tu caché y tus zonas internas. En tus propios cacharros, un resolver visible en un puerto conocido es una ventaja.

Así que: si DoT llega a tu resolver, úsalo y no pierdes nada. Si el puerto 853 está bloqueado, resolved no tiene respuesta y lo que quieres es un proxy DoH delante, dnscrypt-proxy o cloudflared en el loopback con resolved apuntado a él. El cacheo y la validación siguen siendo trabajo de resolved. Solo se mueve el transporte.

Qué trae de verdad tu distribución

El mismo demonio se comporta de forma muy distinta según quién lo empaquetó. Habilitar el servicio es la mitad fácil. La mitad que se salta la gente es enchufarlo a las herramientas de red que la distribución usa de verdad, y en una de estas cuatro genuinamente no está enchufado hasta que lo haces tú.

InstaladoHabilitadonss-resolve conectadoLa configuración llega víaSoportado
Fedora (33+)sísísí, en systemd-libsNetworkManagersí
Ubuntusísísínetplan, luego NM o networkdsí
Debian (12+)paquete apartenono, paquete aparteeliges tú y lo conectas túsí
RHEL / Rocky / Alma (9, 10)sínosíNetworkManager, una vez avisadoTechnology Preview

Validación y caché completa: los ajustes en sí

Son las mismas cuatro líneas en todas partes, porque resolved.conf es resolved.conf en cada distribución. Lo único que cambia es cómo llega el resto de la configuración al demonio, que son las siguientes cuatro secciones.

# /etc/systemd/resolved.conf.d/60-local.conf
[Resolve]
DNSSEC=allow-downgrade
Cache=yes
CacheFromLocalhost=no
StaleRetentionSec=1d

Usa un drop-in en vez de editar /etc/systemd/resolved.conf, porque el fichero principal tiene menos precedencia que cualquier drop-in y una actualización de paquete puede discutírtelo. La numeración es una convención documentada: los fabricantes se quedan del 10 al 40 bajo /usr/, tú te quedas del 60 al 90 bajo /etc/, así que gana lo tuyo.6

En systemd 261 y posteriores hay una quinta línea que merece añadir, porque hasta entonces el tamaño de la caché no era ajustable en absoluto:

DNSCacheSize=16384    # systemd 261+; default 4096, max 2^24

Aplícalo y confirma que el demonio está de acuerdo con el fichero, en vez de suponer que lo leyó:

systemd-analyze cat-config systemd/resolved.conf   # every fragment, in precedence order
systemctl restart systemd-resolved
resolvectl status | grep -E 'DNSSEC|Protocols'
resolvectl statistics                              # verdicts should start moving

DNSSEC=allow-downgrade es el ajuste que pondría en una máquina a la que no voy a hacer de niñera, por las razones de la sección anterior. DNSSEC=yes es el honesto y falla cerrando. Elige a conciencia.

Conectarlo a la pila de red

Encender el servicio es un comando. Conseguir que las propias herramientas de red de tu distribución le entreguen su configuración de DNS es la parte que varía, y es donde «habilitado» y «funcionando como es debido» se separan.

De dónde viene la configuración DNSPara activar DNSSECPara dimensionar la cachéCableado extra necesario
FedoraNetworkManager, automáticamentedrop-indrop-inninguno
Ubuntunetplan, luego NM o networkddrop-in, o por enlace en .networkdrop-inninguno
Debianla pila que hayas elegidodrop-in, o por enlace en .networkdrop-inlibnss-resolve, más la pila de abajo
Familia RHELNetworkManager, una vez avisadodrop-indrop-indns=systemd-resolved

Fedora

La integración más completa de las cuatro, y la única donde todo ya está enchufado.

NetworkManager es dueño de la red y le entrega el DNS a resolved sin que nadie se lo diga. En esta caja no hay ninguna línea dns= en ningún sitio de /etc/NetworkManager/ y aun así funciona, porque NetworkManager detecta el demonio en marcha y lo usa. /etc/resolv.conf es el enlace simbólico al stub, y glibc está conectado porque libnss_resolve.so.2 viene en systemd-libs, que no es opcional:

$ rpm -qf /usr/lib64/libnss_resolve.so.2
systemd-libs-259.9-1.fc44.x86_64

$ grep ^hosts: /etc/nsswitch.conf
hosts: files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns

Así que en Fedora el drop-in de arriba es el trabajo entero. Nada más que conectar.

Ubuntu

Habilitado por defecto, y nss-resolve está conectado de la misma forma. La diferencia es que la configuración suele llegar por netplan:

network:
  version: 2
  ethernets:
    enp1s0:
      dhcp4: true
      nameservers:
        addresses: [9.9.9.9, 149.112.112.112]
        search: [example.com]

Netplan se lo entrega a su renderizador, NetworkManager en escritorio o systemd-networkd en servidor, y el renderizador se lo entrega a resolved. Fíjate en lo que netplan no puede expresar. No hay clave de netplan para DNSSEC=, ni ninguna para DNSOverTLS=. Esas van en el drop-in de arriba, que es donde les corresponde de todos modos.

Si estás con systemd-networkd, los ajustes por enlace también están disponibles de forma nativa en el fichero .network, y un ajuste por enlace gana al global:

# /etc/systemd/network/10-lan.network
[Network]
DNS=9.9.9.9#dns.quad9.net
DNSSEC=yes
DNSOverTLS=yes
Domains=~.

Debian hay que conectarlo a mano

Esta es la que genuinamente no está enchufada, y la razón de que exista la sección de arriba.

Desde Debian 12 systemd-resolved es un paquete aparte, y las notas de la versión son explícitas sobre la actualización: «The new systemd-resolved package will not be installed automatically on upgrades», y «until it has been installed, DNS resolution might no longer work since the service will not be present on the system.»12 Las mismas notas zanjan la cuestión más amplia: «systemd-resolved was not, and still is not, the default DNS resolver in Debian.»

Instalarlo son tres paquetes, no uno, y el segundo es el que todo el mundo se salta:

apt install systemd-resolved libnss-resolve
systemctl enable --now systemd-resolved

libnss-resolve está solo como Suggests:, no como dependencia,13 y Suggests es la única relación sobre la que apt no actúa. Así que nadie lo instala.

La razón de que nadie se dé cuenta es más interesante que la razón de que nadie lo instale. Déjalo fuera y glibc sigue llegando a resolved, porque /etc/resolv.conf apunta al stub y el nss-dns simple habla con él. La mayor parte del demonio sigue funcionando:

Sin libnss-resolveCon él
CachéSí, vía el stubSí
Validación DNSSECSí, ocurre en el demonioSí
DNS sobre TLSSíSí
El bit AD llega a glibcSí, resolv.conf lleva trust-adSí
Seguro contra inseguro contra espurioNo, un solo bitSí
Direcciones con ámbito de enlaceNoSí
El demonio lee /etc/hostsNoSí

Nada en la columna izquierda parece roto, así que nada se arregla. Lo que pierdes es lo que el formato de cable de DNS no puede llevar, y el caso más claro es una dirección con un ámbito encima:

$ getent hosts _gateway            # via nss-resolve
fe80::5054:ff:fe12:3456 _gateway

$ dig +short @127.0.0.53 _gateway  # via the stub, as nss-dns would
192.0.2.1

Mismo nombre, mismo demonio, dos respuestas distintas. Una dirección IPv6 de enlace local no significa nada sin la interfaz a la que está acotada, y no hay ningún sitio en una respuesta DNS donde poner eso, así que el stub solo puede devolver la IPv4. La API nativa sí tiene dónde ponerlo y te da la dirección que de verdad querías.

La fila del veredicto es el mismo problema. AD es un bit, firmado o no. La API nativa separa seguro de inseguro de espurio, que es la diferencia entre «nadie firmó esto» y «alguien firmó esto y otro alguien lo ha estado toqueteando». Una aplicación a la que le importe no puede distinguirlos por el cable.

Esa es la brecha entre el servicio corriendo y el servicio enchufado, y se queda invisible hasta que vas a buscarla.

Comprueba que cuajó:

grep ^hosts: /etc/nsswitch.conf     # wants 'resolve [!UNAVAIL=return] dns'
Cuatro formas de entrar en Debian, y la que no te viene instaladaDE DÓNDE VIENE LA CONFIGURACIÓNCÓMO ENTRAEL DEMONIOifupdown, estáticaifupdown, DHCPsystemd-networkdNetworkManagershim de resolvconf= resolvectlresolved127.0.0.53Y LA PARTE QUE NADIE INSTALAglibc getaddrinfo()libnss-resolveSolo Suggests: sin él glibc sigue funcionando, y el veredicto nunca le llega.
Cuatro formas de entrar en Debian, y la que no te viene instalada

Cómo llega hasta él el resto de tu red depende de en cuál de las pilas de Debian estés.

El paquete declara Provides: resolvconf y Conflicts: resolvconf, openresolv,13 así que se queda con la interfaz de resolvconf por completo. /usr/sbin/resolvconf pasa a ser un enlace simbólico a resolvectl, que es un binario multillamada: invocado bajo ese nombre habla el protocolo de resolvconf(8) y empuja todo lo que le dan directo a resolved.14 Cualquier cosa que ya llame a resolvconf -a sigue funcionando sin modificar, con systemd-resolved como único backend soportado.

No sobrevive toda la interfaz, y los huecos fallan ruidosamente en vez de en silencio:

Opción de resolvconfBajo systemd-resolved
-a <iface>Registra DNS por enlace leído de stdin. La que importa
-d <iface>Desregistra, igual que resolvectl revert
-xMapeada a un dominio de enrutado ~.
-pMarca el enlace como no ruta por defecto (systemd 257+)
-fHace que -a y -d se callen sobre una interfaz que no está
-mAceptada y silenciosamente ignorada
-u, -i, -I, -l, -r, -R, -v, -VNo soportadas. El comando falla

Una trampa que conviene conocer antes de depurarla: el shim solo escribe /etc/resolv.conf cuando ese fichero es un enlace simbólico a /run/systemd/resolve/resolv.conf, y no cuando es un fichero estático.14

  • ifupdown, lo de serie en una instalación de servidor Debian. La estrofa dns-nameservers de /etc/network/interfaces siempre la implementaron los hooks del viejo paquete resolvconf, y systemd-resolved entra en conflicto con ese paquete y no trae hooks propios en /etc/network/if-up.d/. Para una interfaz estática, no te fíes de la estrofa. Pon los servidores en el enlace explícitamente y deja que resolved sea su dueño:

    # /etc/network/interfaces
    auto enp1s0
    iface enp1s0 inet static
        address 192.0.2.10/24
        gateway 192.0.2.1
        up   /usr/sbin/resolvconf -a $IFACE <<< 'nameserver 192.0.2.53'
        down /usr/sbin/resolvconf -d $IFACE
    
  • DHCP sobre ifupdown ya está resuelto. isc-dhcp-client trae hooks con el nombre del demonio, /etc/dhcp/dhclient-enter-hooks.d/resolved-enter y /etc/dhcp/dhclient-exit-hooks.d/resolved,15 así que los servidores DNS de una concesión aterrizan en el enlace correcto sin nada que configurar.

  • systemd-networkd es la opción más limpia y no necesita shim alguno, porque las dos mitades son el mismo proyecto. El DNS por enlace, DNSSEC y DoT son claves nativas del fichero .network, exactamente como en el ejemplo de Ubuntu de arriba.

  • NetworkManager, en un escritorio Debian, necesita que se lo digas una vez:

    # /etc/NetworkManager/conf.d/10-resolved.conf
    [main]
    dns=systemd-resolved
    

Elige una y ten claro cuál elegiste. El modo de fallo aquí no es un demonio que se niega a arrancar, son dos pilas que creen ambas ser dueñas de /etc/resolv.conf, lo que se lee como DNS intermitente y te gasta una tarde. resolvectl status dice en qué enlace aterrizaron los servidores. Si la respuesta es en ninguno, nada está alimentando al demonio, y ese es el fallo.

RHEL, Rocky y Alma

Y aquí está la que merece leerse dos veces. El paquete está ahí, nss-resolve está disponible, NetworkManager puede manejarlo, y la documentación de Red Hat dice esto:

systemd-resolved is an unsupported Technology Preview.16

Esa redacción lleva en las notas de la versión de RHEL 9 desde la 9.0 y sigue ahí en la 9.8. Technology Preview significa que no hay SLA de producción, y Red Hat explícitamente no lo recomienda para uso en producción.

Tampoco está habilitado. NetworkManager es dueño de resolv.conf en estos sistemas, así que encenderlo es un ajuste de NetworkManager y no solo habilitar la unidad:

# /etc/NetworkManager/conf.d/10-resolved.conf
[main]
dns=systemd-resolved
systemctl enable --now systemd-resolved
systemctl reload NetworkManager
resolvectl status          # confirm NM actually handed the servers over

Después de eso el drop-in de arriba se aplica sin cambios, y la validación y la caché se comportan exactamente igual que en Fedora.

Así que en RHEL tienes una decisión que no existe en las otras. ¿Quieres un resolver local validador, con caché y capaz de split DNS en una caja RHEL soportada? Entonces la respuesta soportada no es esta. Es unbound, o dnsmasq a través del propio dns=dnsmasq de NetworkManager, por los que Red Hat sí va a responder.

La etiqueta no es un comentario sobre el código. Es un comentario sobre qué va a respaldar Red Hat con un SLA, y un resolver con caché y validación es una cosa sobre la que los clientes abren tickets. Bastante justo. Pero si estás comprando RHEL por el soporte, ejecutar la resolución de nombres sobre un componente no soportado es una decisión que hay que tomar a conciencia y dejar por escrito, no una en la que hay que caer por inercia porque estaba en el repositorio.

Nada está realmente capado, y así se demuestra

La premisa merece ponerse a prueba, porque «mi distro lo desactivó, voy a tener que recompilar» es el acto reflejo y aquí está equivocado.

systemd tiene dos tipos de opción de compilación completamente distintos, y se habla de ellos como si fueran uno:17

Opción de compilaciónTipoUpstreamFedoraDebian y UbuntuCambiable en tiempo de ejecución
resolvecapacidadactivacompiladatrueno, y ambas la compilan
nss-resolvecapacidadhabilitadacompilada, viene en systemd-libsenabled, viene como libnss-resolveno, y ambas la compilan
dns-over-tlscapacidadautoauto, resuelve a OpenSSLopensslno, y ambas la compilan dentro
opensslcapacidadhabilitadaenabledenabledno, y ambas la habilitan
default-dnssecsolo valor por defectoallow-downgradenonosí, en un drop-in
default-dns-over-tlssolo valor por defectononoel de upstreamsí, en un drop-in
default-mdnssolo valor por defectoyesnonosí, en un drop-in
default-llmnrsolo valor por defectoyesresolvenosí, en un drop-in

Lee la última columna. Cada ajuste del que se ha quejado esta entrada está en la mitad de abajo, y cada uno es un valor por defecto, no una capacidad. El código está compilado dentro en ambas. Nadie quitó nada.

La prueba está más arriba en esta entrada y no necesitó compilador. Sobre el paquete Fedora de serie, con DNSSEC=no cocido dentro, un comando encendió la validación y funcionó entera: una zona firmada autenticada, una sin firmar reportada honestamente, una rota rechazada con un diagnóstico preciso. Si hubiera estado compilada fuera, resolvectl status no habría dicho nunca yes/supported.

Así que comprueba tu propia build antes de echar mano de una cadena de compilación:

# is the crypto there at all
systemctl --version | tr ' ' '\n' | grep -E '^[+-](OPENSSL|GNUTLS|GCRYPT)$'

# ask for the feature and read back what the daemon says it can do
resolvectl dnssec <link> yes
resolvectl status <link> | grep DNSSEC          # yes/supported means the code is there

En esta caja Fedora eso es -GCRYPT +GNUTLS +OPENSSL, que sobra: DNSSEC necesita uno de ellos y DoT está compilado contra OpenSSL.

Lo que las distribuciones sí quitan de verdad

Hay una cosa que ambas quitan, y no está en la lista de la que se queja nadie.

Upstream compila dentro del binario una lista de servidores DNS de respaldo: Cloudflare, Google y Quad9.17 Tanto Fedora como Debian compilan con -Ddns-servers= puesto a nada, y puedes confirmarlo sobre el binario que se distribuye en vez de creerme a mí:

$ strings /usr/lib/systemd/systemd-resolved | grep -ciE 'quad9|one\.one\.one|dns\.google'
0

Nada. Ningún hiperescalar cocido dentro del ejecutable.

Ese es el único sitio donde ambas distribuciones han mejorado a upstream, y merece decirse con claridad dado cuánto de esta entrada ha ido sobre valores por defecto que creo equivocados. Una máquina que pierde su configuración de DNS debería fallar ruidosamente y esperarte. No debería enrutar calladamente cada nombre que buscas a un resolver de otra jurisdicción que tú nunca elegiste. ¿Quieres un respaldo? Pon FallbackDNS= y elige quién es.

Si de verdad necesitas recompilar

Existe un caso real: una build mínima o embebida donde alguien pasó -Ddns-over-tls=false y el transporte está genuinamente ausente. Compruébalo primero con los comandos de arriba, porque es raro y tiene el mismo aspecto que el ajuste estando simplemente apagado.

Si de verdad lo necesitas, recompila el paquete, nunca make install:

# Fedora
dnf download --source systemd
rpmbuild --rebuild --define '_with_upstream 1' systemd-*.src.rpm

# Debian and Ubuntu
apt source systemd
cd systemd-*/ && editor debian/rules       # change the -Ddefault-* flags
dpkg-buildpackage -us -uc -b

No he ejecutado ninguno de los dos en esta máquina, así que trátalos como la forma del trabajo, no como una receta probada. Lo que importa es el principio. systemd es el PID 1, y un make install hecho a mano sobre la copia de tu distribución lo saca del gestor de paquetes: se acabaron las actualizaciones de seguridad, y la siguiente actualización se pelea contigo por los ficheros. Compilar el paquete conserva ambas cosas. Para casi todo el mundo la respuesta honesta es que un drop-in de cuatro líneas hace el mismo trabajo en veinte segundos, que es por lo que esta sección existe sobre todo para disuadirte.

Tres configuraciones que merece la pena usar

No un menú de todas las opciones. Tres posturas, cada una de las cuales defendería de verdad.

Portátil, redes hostilesServidor, tu propia redEstricta
De qué te defiendesDe la cafetería y del portal del hotelDe nada local; el de arriba ya validaDe un camino de resolución en el que no confías
DNSSEC=allow-downgradenoyes
DNSOverTLS=opportunisticnoyes
StaleRetentionSec=1d1dsin poner
Sobrevive a un portal cautivoSín/aNo
Sobrevive a un .arpa filtradoSíSíNo
Sobrevive al puerto 853 bloqueadoSín/aNo
Un atacante puede degradarlaSí, ambos ajustesn/aNo

Un portátil en redes que no controlas. Cifra el transporte, y asume el riesgo de degradación a cambio de que la cosa siga funcionando detrás de un portal cautivo.

# /etc/systemd/resolved.conf.d/60-local.conf
[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=opportunistic
DNSSEC=allow-downgrade
Cache=yes
StaleRetentionSec=1d

Un servidor en una red que sí llevas tú, validando arriba. Ya ocurrió a un salto de distancia. No lo hagas dos veces, y no cojas una dependencia de un resolver público.

[Resolve]
DNSSEC=no
DNSOverTLS=no
Cache=yes
CacheFromLocalhost=no
StaleRetentionSec=1d

Una máquina donde quieres lo de verdad. Estricto en ambos frentes, nada que un atacante pueda degradar, y has comprobado que el de arriba puede llevarlo.

[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes
DNSSEC=yes
Cache=yes

Esa tercera va a romper el DNS inverso si algo en tu camino filtra .arpa, va a romperse del todo si el puerto 853 está bloqueado, y falla cerrando en vez de calladamente. Esos son los términos. Conócelos antes de desplegarla, no después, porque una caja que no puede resolver nada es un viaje largo en coche si no está en la habitación de al lado.

Elijas la que elijas, aplícala y luego comprueba qué decidió el demonio de verdad, no lo que escribiste:

systemd-analyze cat-config systemd/resolved.conf   # every file, in order
systemctl restart systemd-resolved
resolvectl status                                  # the state it is really in

resolvectl status es el oráculo aquí, igual que la build es el oráculo de un sitio Hugo. Un ajuste en un fichero es una intención. Lo que imprime status es lo que está pasando.

El resto de los verbos merece conocerse, porque entre todos responden a casi cualquier pregunta que vayas a tener sobre este demonio sin leer un log:

ComandoQué te dice
resolvectl statusServidores, dominios y protocolos por enlace, y el estado vivo de DNSSEC y DoT
resolvectl query NOMBRELa respuesta, el protocolo usado, el veredicto DNSSEC, y si vino de la caché
resolvectl statisticsAciertos de caché frente a fallos, y el recuento en marcha de seguro/inseguro/espurio
resolvectl show-cacheTodo lo cacheado ahora mismo, por ámbito
resolvectl flush-cachesVacía la caché sin reiniciar el demonio
resolvectl dns LINK ...Fija servidores en un enlace en tiempo de ejecución, sin fichero de configuración
resolvectl dnssec LINK yesEnciende la validación en un enlace, para probar antes de comprometerte
resolvectl domain LINK ~xFija dominios de enrutado y de búsqueda en un enlace
resolvectl revert LINKTira a la basura todos los cambios en tiempo de ejecución de ese enlace
resolvectl show-server-stateSondeo de funciones por servidor: qué se encontró que soporta cada uno de arriba
resolvectl monitorMira consultas y respuestas en vivo, lo que le gana a adivinar

Todo lo del bloque central es solo de tiempo de ejecución y no sobrevive a que un enlace vuelva a levantarse, lo que lo convierte en la forma correcta de probar un ajuste antes de escribirlo en un drop-in. También significa que nmcli device reapply deshará calladamente tus pruebas, así que comprueba status después, no antes.

Los valores por defecto son una postura, no un accidente

Tres funciones en la caja. Una encendida.

Es tentador leer eso como pereza. No lo es. Cada uno de esos valores por defecto lo discutió gente que veía la cola de incidencias al otro lado de la decisión, y Fedora al menos escribió honestamente que no podía con la carga de soporte. Esa es una restricción real y no voy a fingir lo contrario.

Pero un valor por defecto es una postura, y esta dice que una consulta que nadie puede verificar es algo aceptable sobre lo que construir. Tenemos el estándar desde 2005. Los registros publican los registros gratis, el resolver de tu máquina lo implementa entero, y la razón de que esté de brazos cruzados es que demasiada parte de internet nunca firmó nada, así que encenderlo hace que tu máquina sea la que parece rota. Esa es la forma de todo estándar que nadie hace cumplir. Hacerlo como es debido es un coste que cargas tú solo, y el beneficio solo aparece cuando lo han cargado bastantes más.

Que es por lo que el censo es la parte de esto sobre la que yo de verdad iría a actuar. No los ajustes. Los ajustes son veinte minutos. El censo dice que las organizaciones que publican las guías, distribuyen el resolver y alojan el código fuente del mundo en su mayoría no han firmado sus propias zonas, y que gov.uk firmó el ápice y luego apuntó la única web que nadie puede evitar usar a una delegación sin firmar. Alguien hizo la parte difícil y luego nunca comprobó el nombre que la gente teclea.

Solo puedes arreglar lo tuyo. Si llevas una zona, fírmala, deposita el DS, y luego ve a buscar el nombre www como lo haría un visitante y confirma que la cadena sobrevive hasta el final. Es una tarde. Haz eso y el argumento a favor de la validación deja de ser teórico para todo el que resuelva tu nombre, que es la única parte de esto que cualquiera de nosotros puede de verdad remendar.


  1. systemd-resolved.service(8) — «implements a caching and validating DNS/DNSSEC stub resolver»; las cuatro interfaces de cliente, los dos stubs a la escucha, los registros sintéticos, las reglas de enrutado y los cuatro modos de /etc/resolv.conf. ↩︎ ↩︎ ↩︎ ↩︎

  2. resolved.conf(5), tal como viene en systemd 259.9 en Fedora 44 — DNSSEC=, DNSOverTLS=, Cache=, CacheFromLocalhost=, StaleRetentionSec= y la descripción del stub proxy de 127.0.0.54. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. resolved.conf(5) — DNSCacheSize=, MulticastDNSCacheSize= y LLMNRCacheSize=, «Each defaults to 4096», añadidos en systemd 261. ↩︎

  4. RFC 5737 — «IPv4 Address Blocks Reserved for Documentation»: 192.0.2.0/24, 198.51.100.0/24 y 203.0.113.0/24. ↩︎

  5. RFC 3849 — 2001:DB8::/32 reservado para documentación, «to reduce the likelihood of conflict and confusion when relating documented examples to deployed systems». ↩︎

  6. resolved.conf(5), upstream latest — DNSSEC= «Defaults to allow-downgrade», DNSOverTLS= «Defaults to no», la convención de numeración de los drop-in, y la lista completa de directivas sin ninguna opción de DNS sobre HTTPS. ↩︎ ↩︎ ↩︎ ↩︎

  7. Fedora systemd.spec, rawhide — los flags de compilación de meson -Ddefault-dnssec=no, -Ddefault-dns-over-tls=no, -Ddefault-mdns=no, -Ddefault-llmnr=resolve. ↩︎

  8. Debian systemd packaging, debian/rules — -Ddefault-dnssec=no, -Ddefault-llmnr=no, -Ddefault-mdns=no, -Ddns-over-tls=openssl. El systemd de Ubuntu deriva de este empaquetado. ↩︎

  9. Fedora Change: systemd-resolved — Fedora 33 lo hizo el resolver por defecto; cambia el valor por defecto «from the upstream default DNSSEC=allow-downgrade to DNSSEC=no» porque la función «is known to cause compatibility problems with certain network access points» y Fedora «is not prepared to handle an influx of DNSSEC-related bug reports». ↩︎

  10. Fedora Magazine — Use DNS over TLS — la configuración DNSOverTLS=yes recomendada, dada con direcciones IP a secas y sin la forma #hostname para SNI. ↩︎

  11. systemd issue #8639 — «Add support for DNS-over-HTTPS to systemd-resolved», abierta el 2 de abril de 2018, todavía abierta. ↩︎

  12. Debian 12 release notes, 5.2.3 — «The new systemd-resolved package will not be installed automatically on upgrades»; «until it has been installed, DNS resolution might no longer work»; «systemd-resolved was not, and still is not, the default DNS resolver in Debian.» ↩︎

  13. Debian systemd packaging, debian/control — la estrofa de systemd-resolved: Provides: resolvconf, Conflicts: resolvconf, openresolv, Replaces: resolvconf, y libnss-resolve listado solo bajo Suggests:. ↩︎ ↩︎

  14. resolvconf(1), the resolvectl compatibility mode — resolvectl «is a multi-call binary. When invoked as ‘resolvconf’ … it is run in a limited resolvconf(8) compatibility mode»; systemd-resolved «is the only supported backend»; qué opciones están soportadas, ignoradas o rechazadas; y la regla de que /etc/resolv.conf solo se escribe cuando es un enlace simbólico a /run/systemd/resolve/resolv.conf. ↩︎ ↩︎

  15. Debian isc-dhcp-client file list — trae /etc/dhcp/dhclient-enter-hooks.d/resolved-enter y /etc/dhcp/dhclient-exit-hooks.d/resolved. ↩︎

  16. RHEL 9.4 release notes, Technology Previews — «Note that systemd-resolved is an unsupported Technology Preview.» Arrastrado sin cambios desde RHEL 9.0 hasta 9.8. ↩︎

  17. systemd meson_options.txt — la división entre opciones de capacidad (resolve, nss-resolve, dns-over-tls, openssl) y opciones de valor por defecto (default-dnssec, default-dns-over-tls, default-mdns, default-llmnr), y la lista de respaldo dns-servers compilada dentro. ↩︎ ↩︎