DNS: Split-Horizon Mal Implementado — La solución que detiene la locura «inside/outside»

¿Te fue útil?

Si alguna vez has visto un servicio perfectamente sano “fallar aleatoriamente” según desde dónde llegaba la petición, has conocido al split-horizon DNS en su hábitat natural: mal configurado, poco probado y tratado como un truco de magia.

Los síntomas son familiares. Dentro de la oficina, api.example.com funciona. Desde una VPN se agota el tiempo. Desde un pod en Kubernetes resuelve a algo que jurarías que borraste. Desde internet resuelve “correctamente”, excepto que tu monitorización interna ahora está en llamas. El informe del incidente dice “DNS” y todos asienten como si eso explicara algo.

Qué es realmente el split-horizon DNS (y qué no es)

Split-horizon DNS significa que distintos clientes reciben respuestas DNS diferentes para el mismo nombre, según de dónde vienen (IP de origen, interfaz, clave TSIG, vista, ruta del resolutor o una política explícita). Eso es todo. No es inherentemente maligno, y no es inherentemente seguro. Es una política de enrutamiento para nombres.

En la empresa, se usa comúnmente para:

  • Devolver direcciones privadas RFC1918 internamente y direcciones públicas externamente.
  • Devolver objetivos distintos para el mismo nombre de servicio (balanceador interno vs CDN público).
  • Ocultar nombres internos únicamente desde internet público (aunque “ocultar” no es “asegurar”).
  • Soportar aplicaciones heredadas que codifican un único hostname mientras los despliegues abarcan múltiples redes.

Split-horizon no es:

  • Un firewall. Si confías en DNS para impedir acceso, estás construyendo una puerta con un archivo de zona como cerrojo.
  • Un mecanismo de descubrimiento tolerante a la deriva. Si las respuestas “inside” y “outside” no proceden de la misma verdad, diverg irán.
  • Una buena manera de hacer ingeniería de tráfico multi-región a menos que trates a DNS como el sistema lento, cacheado y probabilístico que es.

La parte difícil no es hacer que el split-horizon funcione. La parte difícil es volverlo aburrido. DNS aburrido es DNS fiable.

Por qué el split-horizon falla en empresas reales

El split-horizon falla cuando pierdes el control de tres límites:

1) Autoritativo vs recursivo se confunden

Tus servidores autoritativos deberían responder por las zonas que posees. Tus resolutores recursivos deberían obtener y cachear respuestas para el resto. En entornos rotos, los “servidores DNS” hacen de todo, reenviándose entre sí en bucles, sirviendo datos obsoletos y adivinando qué vista aplica.

2) Los clientes no consultan lo que crees que consultan

Portátiles en Wi‑Fi, servidores en una VPC, pods en Kubernetes y clientes móviles por VPN suelen usar resolutores distintos, dominios de búsqueda distintos y comportamientos de cacheo distintos. Puedes tener “un diseño DNS” en papel y cinco en la realidad.

3) Dos fuentes de verdad se convierten silenciosamente en veinte

“DNS interno” en BIND. “DNS externo” en un proveedor gestionado. Un reenvío condicional en Windows DNS. Una zona privada alojada en la nube. Una regla de reescritura en CoreDNS. Un stub de caché lateral. Un /etc/hosts editado a mano que alguien olvidó. Así es como obtienes el mismo nombre resolviendo a tres IPs según preguntes con dig, nslookup o el runtime de tu aplicación.

Split-horizon no es el villano. La complejidad sin dueño lo es.

Algunos datos e historia que explican el desorden

  • DNS es más antiguo que la mayoría de tus aplicaciones “legacy”. Fue estandarizado en los años 80 para reemplazar el modelo centralizado de HOSTS.TXT.
  • Existe el cache negativo. Si un nombre no existe (NXDOMAIN), ese “no” también puede ser cacheado, controlado por los parámetros SOA de la zona.
  • Los resolutores no están obligados a rotar equitativamente entre registros. Muchos lo hacen, muchos no, y algunos fijan respuestas más tiempo del que quisieras.
  • La truncación UDP es real. Respuestas DNS grandes pueden requerir fallback a TCP; si TCP 53 está bloqueado, obtienes fallos “aleatorios” con DNSSEC o registros TXT grandes.
  • El término “split-horizon” se popularizó en operaciones de red empresariales. Refleja la idea de “split horizon” en enrutamiento: no anunciar rutas de vuelta por donde vinieron.
  • Los CDNs normalizaron la idea de que las respuestas DNS dependen de la ubicación del cliente. Eso hizo que las respuestas basadas en políticas parezcan normales, incluso cuando tu infraestructura no puede soportar la sobrecarga operativa.
  • Los resolutores de caché pueden limitar los TTLs. Tu TTL cuidadosamente elegido de 30 segundos puede convertirse en 300 segundos en algunos resolutores, especialmente de ISP de consumo.
  • Los dominios de búsqueda causan “consultas fantasma”. Una consulta por api puede convertirse en api.corp.example.com, luego api.example.com, luego api—y el resolutor puede cachear fallos intermedios.

Si no recuerdas nada más: DNS es una caché distribuida con opiniones.

Guía rápida de diagnóstico (haz esto primero)

Esta es la lista que uso cuando alguien dice “dentro funciona, fuera no” o “la VPN rompió DNS” y espera que leas la mente.

Primero: establece qué ruta de resolutor usa el cliente fallido

  1. En el host con fallo, identifica los resolutores configurados y los dominios de búsqueda.
  2. Confirma si hay un stub local (systemd-resolved, dnsmasq, nscd) y si tus consultas siquiera llegan a la red.
  3. Comprueba si el cliente está detrás de una cadena de reenvío (DNS de VPN, resolutor de VPC, forwarders on‑prem).

Segundo: compara respuestas desde fuentes autoritativas vs caches recursivos

  1. Pregunta al resolutor recursivo que usa el cliente.
  2. Pregunta directamente a los servidores autoritativos.
  3. Compara: IPs, cadena CNAME, TTL y si la respuesta es NXDOMAIN vs NODATA.

Tercero: verifica la selección de vista o la lógica de reenvío condicional

  1. Confirma la IP de origen que ve el servidor DNS (NAT y proxies importan).
  2. Verifica que las vistas de BIND coincidan con la subred del cliente en el orden que crees.
  3. Revisa los forwarders condicionales: ¿están reenviando la zona correcta, al destino correcto, con recursión habilitada donde se necesita?

Cuarto: confirma que el transporte está sano (UDP/TCP 53)

  1. Confirma que UDP 53 funciona.
  2. Confirma que TCP 53 funciona.
  3. Si hay DNSSEC o respuestas grandes, TCP 53 no es opcional.

Quinto: haz visible el estado de la caché

  1. Mira los TTLs que estás recibiendo.
  2. Vacía caches deliberadamente (stub del cliente, resolutores recursivos) y vuelve a probar.
  3. Si vaciar “lo arregla”, tienes un problema de invalidación de caché. Felicidades: conociste el nivel jefe.

Tareas prácticas: comandos, salidas, decisiones (12+)

Estas son tareas reales que ejecuto durante incidentes. Cada una incluye el comando, qué significa la salida y qué decisión tomar después. Ejecútalas desde tanto una perspectiva “funcionando” como “rota”: LAN interna, VPN, instancia en la nube, pod de Kubernetes y un host público.

Task 1: Ver qué resolutores y dominios de búsqueda usa realmente el host

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.10.0.53
       DNS Servers: 10.10.0.53 10.10.0.54
        DNS Domain: corp.example.com

Significado: Este host consulta a un stub local (systemd-resolved) que reenvía a 10.10.0.53 y 10.10.0.54 y añade el dominio de búsqueda corp.example.com.

Decisión: Si el cliente con fallo usa un servidor DNS distinto al que esperabas, deja de debatir teoría y prueba ese resolutor directamente a continuación.

Task 2: Confirma a qué apunta /etc/resolv.conf (y si es un stub)

cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Jan 15 09:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

Significado: Las consultas van al stub local (típicamente 127.0.0.53), no directamente a tus resolutores corporativos.

Decisión: Si estás solucionando problemas, prueba ambos: el stub (para detectar problemas de caché local) y el resolutor ascendente (para captar split-horizon y problemas de reenvío).

Task 3: Consulta el nombre usando el mismo resolutor que usa el cliente

cr0x@server:~$ dig +time=2 +tries=1 api.example.com @10.10.0.53

; <<>> DiG 9.18.24 <<>> +time=2 +tries=1 api.example.com @10.10.0.53
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4242
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; ANSWER SECTION:
api.example.com.  20  IN A  10.20.30.40

;; Query time: 12 msec
;; SERVER: 10.10.0.53#53(10.10.0.53) (UDP)

Significado: El resolutor interno devuelve una IP privada con un TTL bajo (20s). Es recursivo (ra) y la respuesta es consistente actualmente.

Decisión: Si esto difiere de lo que ve “afuera”, el split-horizon está activo. A continuación, confirma las respuestas autoritativas para cada horizonte.

Task 4: Consulta el resolutor público y compara

cr0x@server:~$ dig +time=2 +tries=1 api.example.com @1.1.1.1

; <<>> DiG 9.18.24 <<>> +time=2 +tries=1 api.example.com @1.1.1.1
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1234
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; ANSWER SECTION:
api.example.com.  300  IN A  203.0.113.77

;; Query time: 18 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)

Significado: El resolutor público devuelve una IP pública con un TTL más largo (300s). Esa es la respuesta “externa”.

Decisión: Decide si esto es intencional. Si lo es, debes asegurar que ambas respuestas apunten a objetivos funcionales y que los clientes se clasifiquen de forma fiable en el horizonte correcto.

Task 5: Identifica los servidores de nombres autoritativos de la zona

cr0x@server:~$ dig +short NS example.com
ns1.dns-provider.net.
ns2.dns-provider.net.

Significado: Internet público considera a ns1/ns2 como autoritativos para example.com.

Decisión: Si DNS interno sirve una autoridad distinta (por ejemplo, un master BIND interno), estás ejecutando dos autoridades. Eso puede estar bien, pero solo con control de cambios disciplinado y automatización.

Task 6: Pregunta directamente a los servidores autoritativos (evita caches)

cr0x@server:~$ dig api.example.com @ns1.dns-provider.net +norecurse

; <<>> DiG 9.18.24 <<>> api.example.com @ns1.dns-provider.net +norecurse
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9000
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; ANSWER SECTION:
api.example.com.  300  IN A  203.0.113.77

Significado: aa significa respuesta autoritativa: la zona pública tiene 203.0.113.77.

Decisión: Si tu resolutor interno debe anular esto, confirma dónde está definida esa anulación (vista BIND, zona privada alojada, reenvío condicional) y si también es autoritativa.

Task 7: Comprueba si una cadena CNAME difiere entre inside y outside

cr0x@server:~$ dig +noall +answer +authority +additional api.example.com @10.10.0.53
api.example.com.  20  IN CNAME api-internal.example.com.
api-internal.example.com.  20  IN A  10.20.30.40

Significado: El horizonte interno usa un CNAME hacia un nombre solo interno.

Decisión: Asegura que el objetivo del CNAME sea resolvible para cada cliente que pueda verlo. Si los clientes VPN son clasificados como “exteriores” pero reciben el CNAME interno, fallarán de una forma que parece un corte de red.

Task 8: Detecta el fallout por dominios de búsqueda (el problema “¿por qué consultó eso?”)

cr0x@server:~$ dig +search api @10.10.0.53

; <<>> DiG 9.18.24 <<>> +search api @10.10.0.53
;; ANSWER SECTION:
api.corp.example.com. 60 IN A 10.9.8.7

Significado: El cliente preguntó por api y el resolutor lo expandió a api.corp.example.com. Eso puede no ser el servicio que pretendías.

Decisión: Si las configuraciones de la app usan nombres cortos, corrige la configuración. No “arregles” DNS añadiendo más registros ambiguos. La ambigüedad escala más rápido que la plantilla de personal.

Task 9: Confirma si el fallback a TCP funciona (respuestas grandes / DNSSEC / truncamiento)

cr0x@server:~$ dig example.com DNSKEY @1.1.1.1 +dnssec +tcp +time=2 +tries=1

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7777
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

Significado: La consulta TCP tuvo éxito. Si UDP funciona pero TCP falla, obtendrás fallos intermitentes cuando las respuestas sean demasiado grandes para UDP sin EDNS0.

Decisión: Si TCP 53 está bloqueado en algún punto entre cliente y resolutor/autoritativo, arréglalo primero. Deja de negociar con firewalls sobre “pero DNS es UDP”. Es ambos.

Task 10: Observa el tráfico DNS real y la IP de origen (la selección de vista depende de ella)

cr0x@server:~$ sudo tcpdump -ni eth0 port 53 -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:22:31.100001 IP 172.16.50.10.51123 > 10.10.0.53.53: 4242+ A? api.example.com. (33)
10:22:31.100220 IP 10.10.0.53.53 > 172.16.50.10.51123: 4242* 1/0/0 A 10.20.30.40 (49)

Significado: El resolutor ve al cliente como 172.16.50.10. Si esperabas una subred diferente (por ejemplo, pool VPN vs pool NAT), la coincidencia de vistas podría estar mal.

Decisión: Ajusta las ACLs de vista para que coincidan con las direcciones que el servidor realmente ve. No hagas coincidir con las direcciones que desearías que fueran verdaderas.

Task 11: Comprueba el orden de vistas de BIND y la asociación de zonas

cr0x@server:~$ sudo named-checkconf -p | sed -n '1,120p'
acl "internal-nets" { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; };
view "internal" {
        match-clients { "internal-nets"; };
        recursion yes;
        zone "example.com" { type master; file "/etc/bind/zones/db.example.com.internal"; };
};
view "external" {
        match-clients { any; };
        recursion yes;
        zone "example.com" { type master; file "/etc/bind/zones/db.example.com.external"; };
};

Significado: Dos archivos de zona diferentes para el mismo nombre de zona, seleccionados por la IP de origen del cliente. El orden de vistas importa: gana la primera coincidencia.

Decisión: Confirma que las subredes de VPN/NAT estén incluidas en internal-nets si deben recibir respuestas internas. Si no, has encontrado tu “locura inside/outside”.

Task 12: Valida la integridad del archivo de zona antes de culpar a la “aleatoriedad DNS”

cr0x@server:~$ sudo named-checkzone example.com /etc/bind/zones/db.example.com.internal
zone example.com/IN: loaded serial 2026020401
OK

Significado: La zona se parsea y carga; el serial es visible. Si esto falla, puede que estés sirviendo una versión antigua o que no se esté cargando en absoluto.

Decisión: Si el serial no es el esperado, encuentra el master real y la ruta de propagación (AXFR/IXFR, despliegue por git, gestión de configuración).

Task 13: Comprueba qué tiene cacheado Unbound (las respuestas obsoletas son un estilo de vida)

cr0x@server:~$ sudo unbound-control lookup api.example.com
api.example.com. 20 IN A 10.20.30.40

Significado: Unbound tiene actualmente una entrada cacheada con TTL restante. Si esa respuesta está mal, los clientes la seguirán viendo hasta que el TTL expire (o la vacíes).

Decisión: Si acabas de cambiar la zona y la caché aún tiene los datos antiguos, vacía ese nombre específico y confirma que los datos autoritativos son correctos.

Task 14: Vacía un nombre cacheado específico (quirúrgico, no “rebootees DNS”)

cr0x@server:~$ sudo unbound-control flush api.example.com
ok

Significado: Entrada de caché eliminada; la siguiente consulta debería obtener datos frescos.

Decisión: Si vaciar lo arregla, necesitas una estrategia de TTL y un proceso de cambios que reconozca el cacheo—especialmente para split-horizon donde dos caches pueden discrepar.

Task 15: Demuestra si la aplicación usa el resolutor del SO o algo distinto

cr0x@server:~$ strace -f -e trace=network -s 128 -p $(pidof myapp) 2>&1 | head -n 20
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 42
connect(42, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
sendto(42, "\252\252\1\0\0\1\0\0\0\0\0\0\3api\7example\3com\0\0\1\0\1", 33, 0, NULL, 0) = 33

Significado: La app habla con 127.0.0.53, el stub local. Si esperabas consultas directas a un resolutor corporativo, tu “arreglo DNS” podría no tocar la ruta real.

Decisión: Decide si arreglas en el stub, en el resolutor ascendente o en la app (por ejemplo, ajustes de caché DNS del JVM). Echar la culpa a “DNS” sin localizar el resolutor es arte performativo.

Task 16: Revisa la caché DNS del JVM (porque Java guarda rencores)

cr0x@server:~$ jcmd $(pidof java) VM.system_properties | egrep 'networkaddress.cache|networkaddress.cache.negative'
networkaddress.cache.ttl=-1
networkaddress.cache.negative.ttl=10

Significado: Un TTL de -1 significa cache para siempre (salvo que una política de seguridad lo anule). Esto anula el failover basado en DNS y puede preservar el “horizonte equivocado” a través de cambios de red.

Decisión: Si dependes de cambios DNS para recuperación, establece TTL sensatos en el runtime o mueve el descubrimiento de servicios fuera de DNS para un failover rápido.

Chiste #1: DNS es el único sistema donde “está cacheado” es a la vez explicación y confesión.

La solución: un diseño de split-horizon que se mantiene sensato

La solución fiable no es “añadir otra vista” ni “bajar el TTL a 5 segundos.” La solución es convertir el split-horizon en un producto deliberado con un único responsable, una única fuente de verdad y una ruta de resolutor predecible.

Paso 1: Decide el modelo de nombres: mismo nombre de zona, o nombres distintos

Tienes dos patrones viables:

  • Mismo nombre de zona (split-horizon clásico): api.example.com existe tanto en vistas internas como externas, con respuestas distintas.

    • Pros: simple para usuarios, un hostname en todas partes.
    • Contras: cada mala clasificación se convierte en un corte; la depuración es más lenta; las caches amplifican los errores.
  • Nombres de zona diferentes (recomendado cuando sea posible): servicios internos bajo api.corp.example.com y público bajo api.example.com.

    • Pros: menos respuestas ambiguas; el “horizonte equivocado” a menudo sigue funcionando porque es un nombre distinto.
    • Contras: requiere cambios en apps/config; las personas deben aprender qué nombre pertenece a cada ámbito.

Mi orientación: si puedes permitirlo, usa nombres distintos. Si no puedes, usa split-horizon con el mismo nombre pero trátalo como código de producción con tests, CI y revisión de cambios.

Paso 2: Elige un flujo autoritativo por horizonte

Si ejecutas dos autoridades distintas (proveedor DNS público e BIND interno), acepta que estás ejecutando dos productos.
Tu trabajo es eliminar las “ediciones manuales” y la deriva:

  • Almacena los datos de zona en control de versiones (incluso si se generan).
  • Genera tanto las zonas internas como externas desde un inventario compartido, con overrides explícitos.
  • Requiere disciplina en los seriales (monótonos, automatizados) y valida antes del despliegue.
  • Haz que la pregunta “¿quién posee este registro?” se responda en un minuto.

Paso 3: Separa DNS autoritativo de DNS recursivo

Aquí es donde muchos entornos se pudren. Ponen BIND en una máquina, habilitan recursión, añaden vistas, añaden reenvíos, y ahora el mismo daemon es:
autoritativo para zonas internas, autoritativo para overrides externos, recursivo para todo lo demás y expuesto a redes que no debería ver.

Un diseño limpio se ve así:

  • Capa autoritativa: sirve zonas internas (y la vista interna de zonas compartidas) solo a resolutores recursivos, no a clientes.
  • Capa recursiva: los clientes hablan con resolutores recursivos; los resolutores hablan con autoritativos y con internet según sea necesario.
  • Capa de política: la lógica de split-horizon vive en pocos puntos (vistas BIND en autoritativos, o reenvíos condicionales en recursors), no repartida por laptops y clientes VPN.

Paso 4: Haz la selección de vista determinista y documentada

Si usas vistas BIND, haz match sobre las IPs que el servidor DNS ve. Eso significa que debes tener en cuenta:

  • Pools de VPN (a menudo subredes distintas a las LAN de oficina)
  • Gateways NAT (los clientes aparecen como la IP del NAT, no su subred original)
  • Resolutores proxy (la IP del forwarder puede ser el “cliente”)
  • Comportamiento dual‑stack (clientes IPv6 pueden evitar suposiciones IPv4)

El fallo más común de split-horizon es una nueva ruta de red (nueva VPN, nuevo NAT, nueva VPC) que no se añadió al ACL “interno”. De repente “usuarios internos” se vuelven “externos”, y las respuestas DNS son incompatibles con el enrutamiento interno.

Paso 5: Mantén respuestas internas y externas compatibles cuando sea factible

Cuando debes compartir un nombre, diseña para que la respuesta “equivocada” falle de forma elegante:

  • Prefiere devolver direcciones enrutable desde ambos horizontes cuando sea posible (por ejemplo, clientes internos también pueden alcanzar el LB público).
  • Si lo interno devuelve IPs privadas, asegura que clientes externos nunca vean esos registros (sin filtraciones por vista equivocada, forwarder o resolutor en la subred errada).
  • Usa CNAMEs con cuidado: amplían el radio de explosión porque el objetivo puede no existir en el otro horizonte.
  • Sé cauto con registros comodín; pueden convertir errores tipográficos en nombres “válidos” que enrutan a sitios costosos.

Paso 6: Estrategia de TTL: deja de hacer culto a “TTL a 5”

TTL es tu palanca para cuánto duran los errores. También es la palanca de cuánto cargas tu infraestructura DNS durante la operación normal.

Guía práctica de TTL:

  • Para registros estables, usa TTL moderados (5–30 minutos). TTL bajos aumentan el volumen de consultas y no garantizan propagación rápida debido a límites de resolutor.
  • Para ventanas de migración, reduce TTLs con antelación (horas a días), luego cambia registros y vuelve a subir TTLs tras la estabilidad.
  • Recuerda el cache negativo: NXDOMAIN puede quedarse y arruinar tu momento de “acabo de crear ese registro”.

Paso 7: Prueba el split-horizon como pruebas de despliegue

Necesitas tests desde cada perspectiva. No “corrí dig una vez desde mi laptop.” Tests que corran continuamente y alerten sobre divergencias:

  • LAN interna: respuesta(s) interna(s) esperada(s).
  • VPN: respuesta(s) interna(s) esperada(s) o comportamiento externo esperado explícitamente.
  • VPC en la nube: respuesta(s) interna(s) esperada(s) si forma parte de la red corporativa.
  • Internet público: respuesta(s) externa(s) esperada(s).
  • Desde los resolutores recursivos directamente: comportamiento autoritativo esperado, TTL correcto, cadena CNAME correcta.

Aquí está la cita operativa que debería perseguir tus revisiones de diseño DNS:

“La esperanza no es una estrategia.” — General Gordon R. Sullivan

Los incidentes de DNS suelen comenzar como esperanza: esperanza de que las caches expiren pronto, esperanza de que los pools VPN nunca cambien, esperanza de que nadie añada un “reenvío temporal”. No operes producción con esperanza.

Tres mini-historias de la vida corporativa

Mini-historia 1: El incidente causado por una suposición equivocada

Una empresa mediana ejecutaba split-horizon en un único par de servidores BIND. La vista interna devolvía IPs privadas para git.example.com; la vista externa devolvía un proxy inverso público.
Durante años funcionó. Silenciosamente. Así es como empiezan la mayoría de los incidentes.

Reemplazaron su concentrador VPN. Nuevo proveedor, arquitectura “moderna”: tráfico cliente peinado a través de un conjunto de gateways NAT. El equipo que actualizó la VPN se centró en autenticación y rendimiento. DNS “no cambió”.

Lunes por la mañana, los ingenieros remotos podían autenticarse a la VPN y alcanzar IPs internas por dirección, pero git.example.com resolvía al proxy externo. El proxy externo hacía SSO que estaba bloqueado por reglas de acceso condicional cuando se accedía desde rangos corporativos. Los ingenieros estaban en VPN, pero DNS los clasificó como “externos”. Lo peor de ambos mundos.

La suposición equivocada fue simple: “Los clientes VPN aún vendrán de las subredes del pool VPN.” No fue así. El servidor DNS solo vio las IPs del gateway NAT. Las vistas BIND matchearon el rango NAT como any, así que cayeron en la vista externa.

La solución fue aburrida: añadir los rangos del gateway NAT al ACL interno, luego crear una sonda de monitorización que realice la misma consulta DNS desde detrás de ese NAT y valide la respuesta interna. También documentaron que “la clasificación de vistas depende de la IP de origen tras NAT”, lo cual debería haber sido obvio, pero los incidentes son la matrícula que pagas por aprender obviedades.

Mini-historia 2: La optimización que salió mal

Una gran empresa quería failover más rápido para una API cliente. Bajaron el TTL de 300 segundos a 5 segundos en el registro A externo y automatizaron el cambio entre dos balanceadores. En demo se veía genial.

Luego llegó producción. Su flota de resolutores recursivos (internos y de algunos ISP grandes) limitó los TTL bajos hacia arriba. No de forma consistente; no predecible. Algunos clientes se re-resolvían rápido, otros seguían usando la dirección vieja por minutos. Mientras tanto, el TTL bajo multiplicó las tasas de consulta. Su proveedor autoritativo lo manejó, pero sus forwarders internos no. La CPU subió. Las tasas de cache hits bajaron. La latencia aumentó. La “optimización” incrementó la probabilidad de fallo justo cuando necesitabas que DNS estuviera tranquilo.

Peor: los registros split-horizon internos para el mismo nombre usaban TTLs distintos y se gestionaban en un sistema diferente. Durante una prueba de failover, los monitores internos aún resolvían al objetivo interno antiguo más tiempo que los clientes externos. El canal de incidentes se llenó de “pero el dashboard dice que sigue caído” mientras los clientes ya estaban de vuelta.

Revirtieron el TTL de 5 segundos, trasladaron el failover a la capa del balanceador donde los cambios de estado se propagan más rápido que las caches DNS, y mantuvieron TTLs DNS en el rango “normal”. También alinearon las políticas de TTL interno y externo para nombres compartidos y empezaron a rastrear tasas de consulta como SLO de primera clase.

Chiste #2: Poner TTL a 5 segundos es el equivalente DNS a intentar ir más rápido quitándote los frenos.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Otra organización mantuvo una separación limpia: servidores autoritativos para zonas internas, resolutores recursivos para clientes y un pequeño conjunto de forwarders condicionales para zonas privadas en la nube. Tenían una regla escrita: “Ningún cliente apunta directamente a DNS autoritativo.” Sonaba pedante. Lo era.

Un día, un nuevo despliegue de archivo de zona interno introdujo un error de sintaxis en la vista interna para una zona compartida. El daemon autoritativo se negó a cargar esa zona. Los resolutores recursivos siguieron sirviendo respuestas cacheadas, comprando tiempo. Su monitorización no solo probaba resolución; probaba carga de zona autoritativa y incrementos de serial.

El on-call recibió una alerta de “zona autoritativa no cargada” antes de que la caché caducara. Revirtieron el cambio del archivo de zona en minutos. Los clientes no lo notaron. Fue el tipo de incidente que no hace buena historia en conferencias porque nada se incendió. Ese es el punto.

La práctica que los salvó fue la más aburrida del DNS: validar configuraciones antes de recargar, monitorizar salud autoritativa y no permitir que clientes golpeen la autoridad directamente. Convirtió un posible incidente a nivel empresa en un simple rollback.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: “Funciona en oficina, falla en VPN”

Causa raíz: Los clientes VPN son NATeados o reciben una subred no incluida en el ACL de vista interno; reciben respuestas DNS externas.

Solución: Actualiza las ACLs de vista para incluir rangos VPN/NAT tal como los ve el servidor DNS; añade sondas continuas desde el egress de la VPN.

2) Síntoma: “dig funciona, la app no”

Causa raíz: La app usa una ruta de resolutor diferente (stub local, DNS en contenedor, caché del runtime del lenguaje) que tu prueba.

Solución: Rastrea las llamadas DNS de la app (strace), inspecciona cachés de runtime (JVM, Go netdns, .NET) y prueba el resolutor exacto que usa.

3) Síntoma: “nslookup muestra una IP, dig muestra otra”

Causa raíz: Servidores por defecto distintos; una herramienta consulta a un resolutor distinto, o la expansión de dominio de búsqueda cambia el nombre consultado.

Solución: Siempre especifica el resolutor con @server; usa nombres completamente calificados con un punto final cuando sea necesario; verifica dominios de búsqueda.

4) Síntoma: “Después de cambiar DNS, algunos clientes todavía van al objetivo viejo”

Causa raíz: Cacheo de resolutores, límites de TTL, caché de aplicación; cache negativo por un NXDOMAIN previo.

Solución: Usa reducciones de TTL planificadas antes de migraciones; vacía caches donde proceda; no confíes en DNS para failover sub-minuto.

5) Síntoma: “Fallos intermitentes con DNSSEC o registros TXT grandes”

Causa raíz: TCP 53 bloqueado; fragmentación UDP o problemas con EDNS0; respuestas truncadas no reintentadas correctamente.

Solución: Permite TCP/UDP 53 end-to-end; verifica soporte EDNS0; prueba con dig +tcp.

6) Síntoma: “Usuarios externos a veces obtienen IPs privadas”

Causa raíz: Datos split-horizon filtrados vía vistas mal aplicadas, un forwarder expuesto a internet o un resolutor público reenviando a interno.

Solución: Nunca expongas resolutores recursivos internos públicamente; restringe recursión; audita cadenas de reenvío; asegura que las zonas internas no se sirvan en contextos públicos.

7) Síntoma: “Los pods de Kubernetes resuelven distinto que los nodos”

Causa raíz: Los pods usan CoreDNS y política DNS del cluster; los nodos usan el resolutor del sistema; el reenvío condicional difiere.

Solución: Alinea los forwarders de CoreDNS con tu estrategia de resolutores recursivos corporativos; añade sondas desde pods; evita hacks de reescritura a menos que puedas testearlos.

8) Síntoma: “Solo algunas sedes/oficinas fallan”

Causa raíz: Forwarders por sitio distintos; reenvío condicional inconsistente; transferencias de zona obsoletas a un resolutor local del sitio.

Solución: Estandariza la configuración de resolutores; monitoriza seriales de zona por sitio; prefiere recursión centralizada con caché local solo cuando sea necesario.

Listas de verificación / plan paso a paso

Paso a paso: reconstruyendo split-horizon para que deje de hacer daño

  1. Inventaria las rutas de resolutor.

    • Lista resolutores recursivos corporativos, resolutores en la nube, resolutores provistos por VPN y cualquier stub local.
    • Decide: qué clientes deben usar qué resolutor, y por qué.
  2. Dibuja el límite de autoridad.

    • ¿Qué servidores son autoritativos para zonas internas?
    • ¿Qué proveedor es autoritativo para zonas públicas?
    • ¿Quién posee cambios en cada una?
  3. Elige el mecanismo de split-horizon.

    • ¿Vistas BIND en autoritativos? ¿Reenvío condicional en recursivos? ¿Zonas privadas en la nube?
    • Minimiza el número de puntos de política. Dos ya son suficientes.
  4. Define reglas de clasificación de vistas.

    • Enumera redes internas tal como las ve el servidor DNS (post-NAT).
    • Incluye egress VPN y rangos NAT de la nube deliberadamente.
  5. Unifica la generación de registros.

    • Genera variantes internas/externas desde datos compartidos con diffs explícitos.
    • Prohíbe ediciones manuales en producción fuera de un proceso de emergencia controlado.
  6. Define política de TTL y cúmplela.

    • Define TTLs estándar por tipo de registro y entorno.
    • Define un playbook de migración para reducir TTLs con antelación.
  7. Construye tests desde cada horizonte.

    • Como mínimo: LAN interna, VPN, nube, internet público.
    • Prueba el nombre, la cadena CNAME y la accesibilidad final (salud HTTP/TCP).
  8. Despliega con puertas de validación.

    • Chequeo de sintaxis de zona, verificación de serial monótono y un reload canario del resolutor antes del despliegue global.
  9. Monitoriza lo que importa.

    • Tasa de NXDOMAIN, tasa de SERVFAIL, fallos de recursión, latencia de consultas y fallos de fallback TCP.
    • Estado de carga de zonas y deriva de seriales entre secundarios.

Checklist operativo: antes de cambiar un registro split-horizon

  • ¿Este nombre existe en más de un horizonte? Enuméralos.
  • ¿El cambio afectará objetivos CNAME que no existen externamente?
  • ¿El TTL es lo bastante bajo ya para soportar la ventana de migración? Si no, redúcelo con antelación.
  • ¿Estás cambiando la fuente autoritativa o solo un override recursivo?
  • ¿Qué sondas de monitorización te dirán que funcionó desde cada horizonte?
  • ¿Tienes un conjunto de registros de rollback listo (y probado)?

Checklist operativo: durante un incidente

  • Identifica la ruta de resolutor desde el cliente fallido.
  • Compara respuestas: resolutor del cliente vs autoritativo.
  • Confirma la coincidencia de vista con captura de paquetes o logs del resolutor.
  • Valida TCP 53.
  • Decide: arreglar clasificación, arreglar datos del registro o vaciar caches (y dónde).
  • Escribe la consulta exacta (con punto final si procede) y la IP del resolutor. La ambigüedad es lo que alarga los incidentes.

Preguntas frecuentes (FAQ)

1) ¿Debo usar split-horizon DNS?

Úsalo cuando realmente necesites que un hostname mapee a objetivos distintos según la ubicación del cliente. Si no, prefiere nombres distintos para servicios internos vs externos. Reduce la ambigüedad y acorta el tiempo de incidente.

2) ¿Es split-horizon DNS lo mismo que “DNS privado” en la nube?

Conceptualmente resultado similar: respuestas distintas según desde dónde consultas. Mecánicamente distinto: las zonas privadas en la nube suelen adjuntarse a VPCs y usar resolutores del proveedor, mientras que el split-horizon clásico usa vistas o políticas en tus servidores DNS.

3) ¿Por qué veo la respuesta “equivocada” solo desde pods de Kubernetes?

Los pods típicamente consultan CoreDNS (o equivalente), que reenvía según la configuración del cluster. Los nodos pueden usar resolutores distintos. Corrige alineando los forwarders de CoreDNS con la estrategia de resolutores recursivos corporativos y prueba desde dentro de un pod.

4) ¿Puedo confiar en TTL para failover rápido?

No para comportamiento sub-minuto. Algunos resolutores limitan TTLs hacia arriba; las aplicaciones cachean; los pools de conexiones siguen usando IPs antiguas. Pon failover rápido en balanceadores o service meshes; mantén DNS para descubrimiento estable y cortes de migración lentos.

5) ¿Cuál es el mayor riesgo operacional con vistas BIND?

La mala clasificación. Si una subred nueva (VPN, NAT, egress de nube) no está incluida en el ACL correcto, esos clientes obtienen la zona equivocada. Entonces depuras “red” cuando el problema real es política.

6) ¿Cómo evito que registros internos se filtren a internet?

No expongas resolutores recursivos internos públicamente. Deshabilita recursión en servidores autoritativos que miren redes no fiables. Restringe transferencias de zona. Audita cadenas de reenvío para que ningún resolutor público reenvíe zonas privadas a infraestructura interna.

7) ¿Por qué vaciar caches a veces “arregla” pero luego vuelve?

Porque vaciaste una caché, no toda la cadena. Existen caches del stub cliente, caches recursivas, caches de runtime de aplicaciones y a veces forwarders intermedios. Si los datos autoritativos están mal, vaciar solo compra una ilusión temporal de competencia.

8) ¿Deben coincidir TTLs internos y externos?

No siempre, pero deberían regirse por la misma política. Si el mismo nombre está en split-horizon, TTLs muy distintos pueden confundir la monitorización, ralentizar la confirmación de incidentes y crear comportamiento asimétrico durante migraciones.

9) ¿Cuál es un patrón seguro para “un nombre en todas partes” sin split-horizon?

Termina tráfico en un endpoint público único que también puedan alcanzar redes internas (LB público con allowlists internas, o VIP de acceso dual). Entonces DNS puede devolver una respuesta global. Esto intercambia complejidad DNS por política de red, que suele ser una ganancia.

10) ¿Cómo monitorizo correctamente split-horizon?

Ejecuta la misma consulta DNS desde cada horizonte y alerta por deriva inesperada. También monitoriza tasa de SERVFAIL/NXDOMAIN, latencia del resolutor y consistencia de seriales de zona. Si solo monitorizas “resuelve o no”, te perderás “resuelve pero no al objetivo correcto”.

Conclusión: próximos pasos prácticos

El split-horizon DNS es sobrevivible si dejas de tratarlo como un truco ingenioso y empiezas a tratarlo como infraestructura con modos de fallo. La solución no es un registro nuevo. Es una nueva disciplina: rutas de resolutor claras, clasificación determinista y una sola verdad para los datos de zona.

Haz esto, en orden

  1. Mapea las rutas de resolutor para LAN, VPN, nube, Kubernetes y un host público. Escríbelas.
  2. Elige tu punto de política: vistas en autoritativos o reenvío condicional en recursivos. Minimiza la cantidad.
  3. Haz la clasificación explícita: enumera subredes tal como las ven los servidores DNS (incluyendo egress NAT/VPN).
  4. Unifica la gestión de registros: genera variantes internas/externas desde datos compartidos; nada de ediciones manuales.
  5. Añade sondas por horizonte y alerta por deriva en respuestas, no solo por éxito de resolución.
  6. Adopta un playbook de migración de TTL para poder cambiar objetivos sin suplicar a las caches por misericordia.

Si quieres que la “locura inside/outside” pare, tu DNS debe volverse aburrido. No mínimo. No ingenioso. Aburrido. Así es como la producción sobrevive.

← Anterior
Picos de CPU cada pocos minutos: la tarea programada que debes revisar primero
Siguiente →
Reparación de errores de disco al arrancar: Qué hacer antes de que empeore

Deja un comentario