Vous êtes sur Debian 13. La moitié de votre parc ne peut plus atteindre git.internal, alors que l’autre moitié y parvient. Quelqu’un a « réparé le DNS » hier et maintenant votre portable résout des noms internes au café, ce qui peut être mignon jusqu’à ce que cela fuit des requêtes et casse les hypothèses zero‑trust.
Ceci est la défaillance classique du split-horizon DNS : les noms internes doivent se résoudre uniquement sur les réseaux internes (ou via VPN), tandis que le DNS public doit rester intact. La partie amusante est que le split-horizon peut échouer à au moins quatre niveaux différents sur Debian 13 : le serveur faisant autorité, le résolveur récursif, le résolveur client (stub) et le routage des serveurs DNS par interface. Si vous devinez mal, vous « corrigez » le problème en empirant l’accès à Internet.
Ce qui casse réellement avec le split-horizon sur Debian 13
Le split-horizon DNS signifie que le même nom interrogé renvoie des réponses différentes selon l’endroit d’où on interroge. Habituellement cela signifie :
- À l’intérieur du réseau :
git.internalse résout en une adresse RFC1918, peut‑être derrière un équilibreur interne. - À l’extérieur du réseau :
git.internaln’existe pas (NXDOMAIN), ou se résout en une adresse publique (rare et risqué), ou ne donne rien d’utile.
En production réelle, « split-horizon » n’est pas une seule fonctionnalité. C’est un accord entre :
- DNS faisant autorité pour vos zones internes, généralement BIND, Knot, PowerDNS, Windows DNS ou un service géré.
- Résolveurs récursifs auxquels les clients s’adressent (Unbound, BIND en récursif, résolveurs d’entreprise, résolveurs fournis par le VPN).
- Résolveur client (stub) et routage par interface : sur Debian 13 c’est souvent
systemd-resolved+ les décisionsresolvectlpar lien/interface. - Domaines de recherche et domaines de routage qui décident si une requête doit aller au « DNS interne » ou au « DNS Internet ».
- Caches partout, y compris le cache négatif (NXDOMAIN) qui peut vous maintenir en erreur pendant des minutes ou des heures après correction.
Debian 13 ajoute une épine : les gens supposent que le système se comporte encore comme « éditez /etc/resolv.conf, c’est réglé ». Sur un système moderne avec systemd-resolved, ce fichier peut être un stub, un lien symbolique, ou un mensonge que vous vous êtes raconté pour dormir tranquille.
Quand le split-horizon « tombe en panne », les défaillances retombent généralement dans l’un de ces cas :
- Mauvais résolveur utilisé (les clients interrogent le DNS public pour des noms internes, ou interrogent le DNS interne pour des noms publics).
- Domaine de routage manquant (les suffixes internes ne sont pas liés à l’interface VPN, donc les requêtes sortent par la route par défaut).
- Divergence des données faisant autorité (les zones internes et externes ont divergé, ou l’une a été supprimée).
- Interaction DNSSEC (les résolveurs validants traitent votre réponse interne comme invalide à cause d’hypothèses de confiance cassées).
- Cache négatif (les clients conservent NXDOMAIN même après correction de la zone).
- Fonctionnalités « d’optimisation » comme EDNS Client Subnet, DNS64, ou un cache agressif se comportent différemment selon les résolveurs et vos hypothèses explosent.
Une règle opérationnelle : le split-horizon doit être explicite et testable. S’il est implicite (« ça marche généralement quand tu es en VPN »), il finira par cesser de fonctionner pendant une réunion importante. Le DNS a le sens du timing.
Une citation qui tient encore en ops : « L’espoir n’est pas une stratégie » — idée paraphrasée, souvent attribuée à plusieurs responsables opérations.
Blague #1 : le DNS est le seul système où « c’est mis en cache » est à la fois une explication et une menace.
Faits intéressants et contexte historique
- Le split-horizon précède le cloud de plusieurs décennies. Il est né des entreprises publiant des noms internes tout en exposant une vue externe filtrée pour des partenaires et l’Internet naissant.
- Les « views » de BIND sont devenues le mécanisme canonique pour servir des réponses différentes selon le sous‑réseau client ; ce concept influence encore la façon dont beaucoup d’équipes pensent le DNS partagé.
- Le cache négatif est normalisé. Depuis la RFC 2308, les résolveurs peuvent mettre en cache NXDOMAIN selon le SOA de zone ; un enregistrement « réparé » peut sembler cassé plus longtemps que prévu.
- Les domaines de recherche étaient l’astuce principale. Avant le routage DNS par interface, les admins utilisaient largement
searchdansresolv.conf. Ça fonctionnait… jusqu’à ce que les portables rencontrent plusieurs réseaux et VPN. - Le piège du « .local » est historique, pas théorique. mDNS utilise
.local(RFC 6762). Si vous l’avez utilisé pour du DNS unicast interne, vous avez probablement des fantômes dans votre résolveur. - Les travaux ICANN 2013 sur les collisions de noms ont montré combien d’entreprises utilisaient des TLD privés qui plus tard entraient en conflit avec le DNS public ou de nouveaux TLD.
- systemd-resolved a introduit le routage DNS par lien pour que le DNS du VPN ne soit utilisé que pour certains domaines. Cela résout un vrai problème, mais signifie aussi que le vieux modèle mental est obsolète.
- EDNS0 a changé le comportement des tailles de paquets. Les résolveurs modernes envoient souvent des paquets UDP plus grands ; si le PMTU ou les règles de pare-feu sont incorrectes, vous obtenez des timeouts étranges qui ressemblent à « DNS instable ».
- Certaines résolveurs font du « NXDOMAIN cut » et un cache NSEC agressif. Avec DNSSEC, un résolveur peut inférer la non‑existence de noms adjacents. Si votre histoire DNSSEC interne est confuse, le débogage devient une danse interprétative.
Playbook de diagnostic rapide (vérifier d’abord/ensuite)
Premier : confirmer quel chemin résolveur le client utilise réellement
- Est‑ce que
systemd-resolvedtourne et/etc/resolv.confest un stub ? - Quels serveurs DNS sont configurés par interface (VPN vs LAN vs Wi‑Fi) ?
- Voyez‑vous des domaines de routage (~corp.example) appliqués au lien VPN ?
Deuxième : reproduire avec un outil, un nom, un serveur
- Choisissez un nom interne unique (par ex.
git.internal). - Interrogez le résolveur configuré, puis interrogez directement le serveur faisant autorité.
- Comparez les réponses, les TTL et si vous obtenez NXDOMAIN vs SERVFAIL vs timeout.
Troisième : chercher les caches et interférences de politique
- Videz le cache client et réessayez.
- Vérifiez le TTL négatif dans le SOA.
- Vérifiez l’état de validation DNSSEC si vous validez.
- Inspectez le pare‑feu/MTU si vous voyez des timeouts intermittents.
Quatrième : ne touchez la configuration qu’après
- Si le client interroge le mauvais serveur : corrigez le routage DNS par lien ou la configuration NetworkManager/VPN.
- Si le résolveur transfert mal : corrigez le transfert conditionnel / les zones stub.
- Si les données d’autorité sont incorrectes : corrigez la zone, puis fixez les attentes concernant le délai de cache.
Tâches pratiques (commandes, sorties, ce que ça signifie, décision)
Voici les tâches que j’exécute réellement lorsqu’un incident split-horizon arrive. Chaque tâche inclut une sortie réaliste d’exemple et la décision qui en découle. Remplacez noms/IP par votre réalité.
Task 1 — Vérifier si systemd-resolved est dans le chemin
cr0x@server:~$ systemctl status systemd-resolved --no-pager
● systemd-resolved.service - Network Name Resolution
Loaded: loaded (/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-30 09:12:18 UTC; 2h 4min ago
Docs: man:systemd-resolved.service(8)
Main PID: 612 (systemd-resolve)
Status: "Processing requests..."
Signe : Vous n’êtes pas face à « juste resolv.conf ». Il y a un résolveur stub et un cache.
Décision : Utilisez resolvectl pour inspecter le DNS par lien et les domaines de routage ; ne modifiez pas aveuglément /etc/resolv.conf.
Task 2 — Inspecter la réalité de /etc/resolv.conf
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Dec 30 09:12 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf
Signe : Les applications lisant /etc/resolv.conf sont pointées vers le stub local (typiquement 127.0.0.53).
Décision : Configurez le DNS via systemd/network manager, pas en réécrivant /etc/resolv.conf (sauf si vous désactivez volontairement resolved).
Task 3 — Voir quels serveurs DNS et domaines sont appliqués par lien
cr0x@server:~$ resolvectl status
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 1.1.1.1
DNS Servers: 1.1.1.1 8.8.8.8
Link 2 (enp1s0)
Current Scopes: DNS
Protocols: +DefaultRoute
DNS Servers: 192.168.10.53
DNS Domain: office.example
Link 5 (tun0)
Current Scopes: DNS
Protocols: -DefaultRoute
DNS Servers: 10.60.0.53
DNS Domain: ~internal.example ~svc.internal.example
Signe : Le lien VPN a des domaines de routage (~internal.example) et pas de route par défaut pour DNS. C’est bon : seuls ces domaines doivent aller vers le DNS du VPN.
Décision : Si les noms internes échouent, vérifiez si le nom interrogé correspond bien au domaine de routage. Si c’est git.internal mais que votre domaine de routage est ~internal.example, vous avez trouvé la discordance.
Task 4 — Tester un nom interne via le stub (ce que voient les applis)
cr0x@server:~$ resolvectl query git.internal.example
git.internal.example: 10.60.12.44 -- link: tun0
-- Information acquired via protocol DNS in 21.4ms.
-- Data is authenticated: no
Signe : Le client route cette requête sur l’interface VPN. Bien. La réponse existe.
Décision : Si les applications échouent encore, vous avez probablement un cache au niveau de l’application, un nom erroné, ou un problème TLS/SNI — pas forcément un split-horizon DNS.
Task 5 — Quand ça échoue : distinguer NXDOMAIN vs SERVFAIL vs timeout
cr0x@server:~$ resolvectl query git.internal
git.internal: resolve call failed: 'git.internal' not found
Signe : Non trouvé dans le chemin DNS utilisé. C’est généralement NXDOMAIN, mais resolved l’abstrait.
Décision : Lancez dig contre des serveurs spécifiques pour vérifier si c’est vraiment NXDOMAIN, ou un problème de politique/routage.
Task 6 — Interroger directement le résolveur VPN
cr0x@server:~$ dig +noall +answer @10.60.0.53 git.internal A
git.internal. 300 IN A 10.60.12.44
Signe : Le résolveur interne connaît ce nom (et l’apex de zone est probablement internal. ou vous avez un TLD privé délégué).
Décision : Assurez‑vous que le client route réellement les requêtes pour git.internal vers le résolveur VPN. Si vos domaines de routage sont seulement ~internal.example, ce nom fuira vers le DNS public et échouera.
Task 7 — Interroger directement le résolveur public (pour détecter une fuite)
cr0x@server:~$ dig +noall +comments +answer @1.1.1.1 git.internal A
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 29144
Signe : Le DNS public renvoie NXDOMAIN. Si des clients interrogent des résolveurs publics pour git.internal, ils échoueront et peuvent mettre en cache l’échec.
Décision : Corrigez le routage DNS split (domaines de routage/transfert conditionnel). Ne créez pas d’enregistrements publics pour des noms internes comme « solution rapide ». C’est ainsi que vous transformez une architecture interne en architecture Internet.
Task 8 — Vérifier les domaines de recherche et pourquoi les noms courts piègent
cr0x@server:~$ resolvectl domain
Link 2 (enp1s0): office.example
Link 5 (tun0): ~internal.example ~svc.internal.example
Signe : Seul office.example est un domaine de recherche ; les domaines VPN sont en routage uniquement. Une requête pour git peut devenir git.office.example, pas git.internal.example.
Décision : Utilisez des noms pleinement qualifiés (FQDN) pour les outils critiques. Si les humains doivent taper des noms courts, définissez des domaines de recherche cohérents et assumez le coût opérationnel.
Task 9 — Vider les caches (côté client) pour éliminer un NXDOMAIN obsolète
cr0x@server:~$ resolvectl flush-caches
cr0x@server:~$ resolvectl statistics
DNSSEC supported by current servers: no
Transactions: 1283
Cache size: 0
Cache hits: 0
Cache misses: 43
Signe : Cache vidé. Si le problème « se règle » après cela, vous batailliez contre un échec mis en cache.
Décision : Inspectez le TTL négatif dans votre SOA et ajustez‑le si vous observez des NXDOMAIN à longue durée après des changements.
Task 10 — Vérifier le SOA de la zone faisant autorité pour le TTL négatif (le saboteur silencieux)
cr0x@server:~$ dig +noall +answer @10.60.0.53 internal.example SOA
internal.example. 3600 IN SOA ns1.internal.example. hostmaster.internal.example. 2025123001 3600 600 1209600 3600
Signe : Le dernier champ SOA (3600) est le TTL du cache négatif en pratique moderne. NXDOMAIN peut rester pendant une heure.
Décision : Pour les zones qui changent fréquemment, réduisez le TTL négatif (p.ex. 60–300 secondes). Ne le mettez pas à 0 sauf si vous aimez une charge de requêtes accrue et de nouveaux modes de panne.
Task 11 — Valider que le serveur faisant autorité sert des réponses différentes selon le sous‑réseau client (cas BIND views)
cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A
git.internal.example. 300 IN A 10.60.12.44
cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A +subnet=203.0.113.10/32
git.internal.example. 300 IN A 198.51.100.44
Signe : Le serveur effectue un comportement split-horizon (peut‑être via des views ou une logique ECS). Cette seconde réponse est un signal d’alerte si elle n’est pas intentionnelle.
Décision : Si vous n’utilisez pas explicitement EDNS Client Subnet, désactivez‑le sur les récursifs ou assurez‑vous que les views se basent sur l’IP source, pas sur ECS. ECS peut fuir la topologie et compliquer le cache.
Task 12 — Vérifier que le bon serveur DNS est utilisé du point de vue des applications
cr0x@server:~$ getent ahosts git.internal.example
10.60.12.44 STREAM git.internal.example
10.60.12.44 DGRAM
10.60.12.44 RAW
Signe : La résolution NSS (glibc) résout le nom. Si dig fonctionne mais getent échoue, vous avez probablement des problèmes NSS, un DNS en conteneur, ou des bibliothèques de résolveur différentes.
Décision : Considérez getent comme « ce que voient la plupart des applis ». Déboguez le DNS au niveau NSS, pas seulement avec dig.
Task 13 — Confirmer que NetworkManager/VPN a transmis correctement le DNS
cr0x@server:~$ nmcli dev show tun0 | sed -n '1,80p'
GENERAL.DEVICE: tun0
GENERAL.TYPE: tun
GENERAL.STATE: 100 (connected)
IP4.DNS[1]: 10.60.0.53
IP4.DOMAIN[1]: ~internal.example
IP4.DOMAIN[2]: ~svc.internal.example
Signe : Le profil VPN pousse le serveur DNS et les domaines de routage. Bien. Si cela manque, resolved ne peut pas faire de choix intelligents.
Décision : Corrigez le profil VPN (ou son plugin) pour pousser les bons DNS et domaines. Ne « corrigez » pas en codant en dur des résolveurs globaux.
Task 14 — Inspecter le forwarding Unbound si vous exécutez un résolveur récursif local
cr0x@server:~$ sudo unbound-control status
version: 1.19.0
verbosity: 1
threads: 2
modules: 2 [ subnetcache validator ]
uptime: 7261 seconds
options: control(ssl)
Signe : Unbound tourne et peut être le composant effectuant le transfert conditionnel.
Décision : Vérifiez qu’Unbound a des forward-zone ou stub-zone explicites pour les domaines internes ; sinon il interrogera l’Internet public et échouera (ou fuira).
Task 15 — Voir quel processus écoute sur le port 53 localement
cr0x@server:~$ sudo ss -lntup | grep ':53 '
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=15))
Signe : Seul le listener stub est sur 127.0.0.53. Si vous attendiez dnsmasq/unbound, il n’écoute pas.
Décision : Décidez si vous voulez systemd-resolved seul, ou un recursor local. Si vous en ajoutez un, planifiez explicitement la propriété des ports et l’intégration.
Cas n°33 : Split-horizon DNS en panne
Ce cas se présente en trois variations. Même film, acteurs différents.
Variation A : La zone interne « fonctionnait » jusqu’à l’arrivée des portables Debian 13
Pendant des années, les clients suivaient une règle simple : le Wi‑Fi interne fournissait des serveurs DNS internes via DHCP. Les réseaux externes fournissaient des résolveurs publics. On utilisait un VPN, mais cela importait peu, car les noms internes étaient principalement nécessaires sur site.
Puis le travail à distance est devenu la norme. Le VPN est devenu par défaut. Quelqu’un a activé le split DNS « correctement » pour que seuls les domaines internes aillent vers le DNS interne, et le public reste public. Objectif louable.
Mais le schéma de nommage interne était un tas de noms courts et un TLD privé (comme .internal), tandis que le VPN poussait des domaines de routage pour ~internal.example. Debian 13 n’a pas deviné ce que vous vouliez. Il a routé git.internal vers des résolveurs publics. NXDOMAIN. Mis en cache. Panne.
Variation B : Le serveur faisant autorité était correct ; la politique client était fausse
Les admins ont prouvé que l’enregistrement existait en interrogeant directement le DNS interne. Tout le monde acquiesce. Puis ils « corrigent » le client en ajoutant les DNS internes comme résolveurs globaux.
Maintenant le DNS public devient lent. Pire, les résolveurs internes commencent à recevoir des requêtes pour des domaines publics qu’ils ne peuvent pas résoudre efficacement (ou qu’ils ne sont pas autorisés à résoudre). Quelqu’un ajoute un forwarding pour « . » vers un résolveur public. Félicitations, vous avez construit une chaîne de récursion qui fuit des requêtes internes et ajoute de la latence.
Variation C : Le chemin du résolveur était correct ; le cache donnait l’illusion d’erreur
La veille, un changement de zone avait supprimé brièvement un enregistrement. Les résolveurs ont mis en cache NXDOMAIN avec un TTL négatif d’une heure. L’enregistrement est revenu 5 minutes plus tard. Les gens ont continué à voir des échecs pendant l’heure suivante.
C’est alors que quelqu’un exécute un script de « vidage de cache DNS » sur tout le parc avec sudo. Ça aide. Ça devient aussi le rituel. C’est ainsi que se forment les religions tribales du DNS.
Trois architectures viables (et laquelle choisir)
Design 1 : Split-horizon faisant autorité via BIND views (classique)
Comment ça marche : Le même serveur faisant autorité répond aux clients internes depuis une view interne et aux clients externes depuis une view externe. Les clients interrogent simplement « le serveur DNS », et le serveur choisit selon l’adresse source.
Avantages : Contrôle central ; les clients restent simples ; les anciens appareils fonctionnent.
Inconvénients : La politique basée sur l’IP source échoue quand les clients passent par du NAT ou des résolveurs partagés ; plus complexe en multi‑cloud ; les erreurs sont catastrophiques car l’autorité est la source de vérité.
Choisir quand : Vos frontières réseau sont claires, vous contrôlez la récursion, et vous pouvez identifier de façon fiable les réseaux clients.
Design 2 : Une vue autoritaire unique, split réalisé en récursion via le forwarding conditionnel (entreprise moderne)
Comment ça marche : Les zones faisant autorité sont propres et cohérentes. Le comportement split se fait dans les résolveurs récursifs : les résolveurs internes savent joindre les serveurs faisant autorité internes ; les résolveurs publics non. Les clients utilisent des recursors internes seulement quand c’est approprié (VPN/on‑prem).
Avantages : Architecture DNS plus propre ; histoire DNSSEC plus simple ; évite « deux sources de vérité ».
Inconvénients : Nécessite un routage client correct (domaines par lien). Si cette partie est erronée, les requêtes internes fuient vers des résolveurs publics et échouent.
Choisir quand : Vous avez des réseaux mixtes (bureau, domicile, VPN) et vous voulez un comportement déterministe sans dépendre de l’IP source sur l’autorité.
Design 3 : Split DNS côté client via les domaines de routage de systemd-resolved (meilleur pour postes Debian 13)
Comment ça marche : Le client a plusieurs serveurs DNS. Pour des domaines spécifiques, les requêtes vont au DNS du VPN. Le reste va au DNS public. C’est précisément ce pour quoi systemd-resolved est bon, lorsqu’il est configuré correctement.
Avantages : Empêche les fuites ; supporte plusieurs réseaux ; explicite ; débogable avec resolvectl.
Inconvénients : Vous devez être discipliné sur le nommage des domaines. Les TLD privés et les noms courts multiplient les douleurs. Certaines applis contournent le stub.
Choisir quand : Vous avez des portables, du VPN, et vous tenez à ne pas casser l’Internet public depuis vos machines clientes.
Si vous gérez des postes Debian 13, Design 3 est la réponse pragmatique dans la majorité des cas. Si vous gérez des serveurs dans un réseau stable, le Design 2 est généralement plus propre. Le Design 1 reste valide, mais il est facile de se tromper à grande échelle.
Configurations concrètes qui réglent le problème (sans « hardcoder 8.8.8.8 »)
Option A : Corriger les domaines de routage par lien sur Debian 13 (systemd-resolved)
L’idée clé : faire en sorte que les suffixes internes aillent vers le DNS du VPN, et garder le DNS global pour le reste.
VPN géré par NetworkManager (recommandé sur postes)
Assurez‑vous que votre connexion VPN configure DNS et domaines de routage. Vous pouvez le faire dans le profil de connexion :
cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns "10.60.0.53 10.60.0.54"
cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns-search "~internal.example ~svc.internal.example"
cr0x@server:~$ nmcli connection up corp-vpn
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/12)
Signe : Le ~ marque des domaines en routage uniquement, pas des domaines de recherche. C’est ce qu’il faut : seules les requêtes pour ces suffixes iront au DNS du VPN.
Décision : Si vous dépendez de git.internal (sans suffixe), arrêtez‑vous et renommez ou fournissez des FQDN. Les domaines de routage correspondent aux suffixes, pas à l’intuition.
systemd-networkd (serveurs ou installations minimalistes)
Pour une interface VPN, vous pouvez définir DNS et domaines dans des unités .network. Extrait d’exemple :
cr0x@server:~$ sudo sed -n '1,120p' /etc/systemd/network/50-tun0.network
[Match]
Name=tun0
[Network]
DNS=10.60.0.53
DNS=10.60.0.54
Domains=~internal.example ~svc.internal.example
Signe : Cela attache des domaines de routage à l’interface VPN. Le DNS public reste global.
Décision : Redémarrez le réseau avec précaution. Sur des systèmes distants, faites‑le en fenêtre de maintenance ou avec accès console.
Option B : Forwarding conditionnel dans Unbound (bon pour un résolveur « intelligent » local)
Si vous voulez un résolveur récursif local qui transfère les zones internes vers le DNS interne (et résout le reste normalement), Unbound est simple et excellent.
Exemple /etc/unbound/unbound.conf.d/forward-internal.conf :
cr0x@server:~$ sudo sed -n '1,120p' /etc/unbound/unbound.conf.d/forward-internal.conf
forward-zone:
name: "internal.example."
forward-addr: 10.60.0.53
forward-addr: 10.60.0.54
forward-zone:
name: "svc.internal.example."
forward-addr: 10.60.0.53
forward-addr: 10.60.0.54
Signe : Les requêtes pour ces zones vont aux résolveurs internes ; le reste est résolu via les hints racines ou vos upstreams configurés.
Décision : Si vous déployez cela sur des postes, préférez quand même les domaines de routage par lien pour éviter de fuir les noms internes hors VPN.
Redémarrez et vérifiez :
cr0x@server:~$ sudo systemctl restart unbound
cr0x@server:~$ sudo unbound-control reload
ok
Option C : BIND views sur l’autorité (utile, mais prudence)
Les views sont puissantes : elles vous permettent de servir des fichiers de zone différents selon les clients. Elles cachent aussi bien les bugs jusqu’à 2 h du matin.
Structure d’exemple (simplifiée) pour named.conf :
cr0x@server:~$ sudo sed -n '1,200p' /etc/bind/named.conf.local
acl "internal-nets" { 10.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; };
view "internal" {
match-clients { internal-nets; };
recursion no;
zone "internal.example" {
type master;
file "/etc/bind/zones/db.internal.example.internal";
};
};
view "external" {
match-clients { any; };
recursion no;
zone "internal.example" {
type master;
file "/etc/bind/zones/db.internal.example.external";
};
};
Signe : Les clients des réseaux internes voient des réponses internes ; tous les autres voient la zone externe (qui peut être vide ou renvoyer NXDOMAIN selon votre configuration).
Décision : Assurez‑vous que vos vérifications de monitoring interrogent les deux views. La plupart des organisations ne monitorent que depuis l’intérieur, puis sont surprises que des partenaires ne puissent résoudre rien.
Option D : dnsmasq pour un petit site ou un labo
dnsmasq peut faire du forwarding split et du cache dans un petit démon. C’est parfait jusqu’à ce que vous transformiez accidentellement votre portable en serveur DNS pour tout le café.
Extrait d’exemple :
cr0x@server:~$ sudo sed -n '1,120p' /etc/dnsmasq.d/split-dns.conf
server=/internal.example/10.60.0.53
server=/svc.internal.example/10.60.0.53
no-resolv
server=1.1.1.1
server=8.8.8.8
cache-size=10000
Signe : Les zones internes sont transférées vers le DNS interne, tout le reste vers des résolveurs publics.
Décision : Si vous utilisez cela, verrouillez l’attachement aux interfaces et les règles de pare‑feu. « Résolveur ouvert accidentel » est une carrière qui se restreint.
Blague #2 : Lancer un résolveur ouvert, c’est comme laisser sa voiture déverrouillée avec un panneau « trajets gratuits » — quelqu’un acceptera l’offre.
Erreurs courantes (symptôme → cause racine → correctif)
1) Les noms internes échouent seulement en VPN
Symptôme : Connecté au VPN ; git.internal.example échoue, mais les sites publics fonctionnent.
Cause racine : Le serveur DNS VPN est configuré, mais les domaines de routage ne le sont pas. Les requêtes pour les domaines internes vont toujours aux résolveurs publics.
Correctif : Poussez les domaines de routage via le profil VPN (~internal.example) et vérifiez avec resolvectl status. Ne mettez pas le DNS VPN en résolution globale sauf si vous le souhaitez explicitement.
2) Le DNS public devient lent ou instable après avoir « corrigé » le DNS interne
Symptôme : Après avoir ajouté des DNS internes globalement, la navigation ralentit ; résolutions publiques intermittentes.
Cause racine : Les résolveurs internes ne sont pas conçus/autorisé à faire la récursion pour Internet, ou ils transfèrent vers un autre résolveur avec latence et filtrage. Vous avez inséré un saut inutile.
Correctif : Utilisez un routage split : zones internes vers DNS interne, le reste vers DNS public ou un recursor d’entreprise approprié.
3) Ça marche avec dig, mais échoue dans les applications
Symptôme : dig renvoie le bon A record, mais curl/git/apt échouent à résoudre.
Cause racine : L’appli utilise un chemin de résolveur différent (DNS en conteneur, resolv.conf statique dans un chroot, mauvaise configuration NSS), ou elle a mis en cache un échec.
Correctif : Utilisez getent ahosts, inspectez le /etc/resolv.conf du conteneur, vérifiez resolvectl et videz les caches. Validez que l’appli n’est pas verrouillée sur un DNS personnalisé.
4) Ça échoue pour AAAA mais marche pour A (ou inversement)
Symptôme : IPv4 fonctionne ; la résolution IPv6 mène à des timeouts ou de mauvaises réponses.
Cause racine : Absence d’enregistrements AAAA en interne, hypothèses DNS64/NAT64 cassées, ou pare‑feu bloquant les réponses DNS plus volumineuses lorsque AAAA déclenche plus de données.
Correctif : Décidez si vous supportez IPv6 en interne. Si oui, publiez des AAAA et assurez‑vous que MTU/pare‑feu gèrent EDNS0. Sinon, envisagez de filtrer AAAA au recursor (dernier recours) et soyez explicite.
5) SERVFAIL intermittent depuis des résolveurs validants
Symptôme : Certains clients obtiennent SERVFAIL ; d’autres non ; le serveur faisant autorité semble répondre.
Cause racine : Échec de validation DNSSEC dû à une signature incohérente, chaîne brisée, ou split-horizon retournant un matériel DNSSEC différent de l’attendu.
Correctif : Soit signez correctement les zones internes et gérez les ancres de confiance, soit désactivez la validation pour ces zones sur le résolveur validant. Le DNSSEC à moitié géré est pire que pas de DNSSEC.
6) Les noms internes courts cessent de fonctionner après le passage aux domaines de routage
Symptôme : Les utilisateurs tapent git et cela marchait avant ; maintenant non.
Cause racine : Les domaines de recherche ont changé ou disparu ; les domaines de routage n’expansent pas les noms courts.
Correctif : Passez les utilisateurs aux FQDN pour les services critiques. Si vous devez garder des noms courts, ajoutez des domaines de recherche en connaissance de cause et testez les collisions (p.ex. git.office.example vs git.internal.example).
7) La « correction » provoque la fuite des noms internes dans les logs DNS publics
Symptôme : Rapports de sécurité indiquent des noms internes vus par des résolveurs publics ; préoccupations de confidentialité.
Cause racine : Les clients interrogent des résolveurs publics pour des suffixes internes car le routage split DNS est absent ou erroné.
Correctif : Enforcez les domaines de routage et utilisez des recursors internes qui refusent de transférer les zones internes vers l’Internet public. Surveillez les fuites en interrogeant des résolveurs publics depuis des clients contrôlés et en vérifiant les patterns NXDOMAIN.
Trois mini‑histoires du monde corporate (anonymisées, plausibles, techniquement exactes)
Histoire 1 : La panne causée par une mauvaise hypothèse
Ils étaient une entreprise de taille moyenne avec un DNS interne propre : corp.example pour les utilisateurs, svc.corp.example pour les services. L’équipe VPN a ajouté le split DNS et poussé ~svc.corp.example vers les postes. Tout semblait correct dans leur ticket.
L’hypothèse erronée était subtile : ils croyaient que tous les services internes étaient sous svc.corp.example. En réalité, un service legacy était nommé jira.corp.example, pas jira.svc.corp.example. Les utilisateurs ne le savaient pas ; ils avaient des favoris.
Quand des personnes se sont connectées depuis chez elles, jira.corp.example est allé au DNS public, a retourné NXDOMAIN, et a été mis en cache. Quelques utilisateurs ont essayé plus tard depuis le réseau du bureau et ont toujours obtenu NXDOMAIN pendant un moment, car leurs caches locaux avaient été empoisonnés par « n’existe pas ».
Engineering a passé la matinée à vérifier le serveur Jira et son équilibreur. Tout était OK. Le commandant d’incident a finalement exigé une reproduction unique avec des DNS explicites. C’est là que la discordance entre les domaines de routage et les conventions de nommage est devenue évidente.
La correction était ennuyeuse : ajouter ~corp.example aux domaines de routage VPN et publier un petit « contrat DNS » interne : quels suffixes sont internes, quels suffixes sont publics, et qui gère les changements. Le grand changement fut culturel : plus de « les noms internes sont évidents ». Ils ne le sont pas.
Histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre entreprise avait deux recursors internes et un upstream public. Quelqu’un a remarqué des pics de latence DNS et a « optimisé » en plaçant les recursors internes comme résolveurs globaux partout, y compris sur les portables hors VPN. Leur raisonnement : les recursors internes ont de meilleurs caches et peuvent forwarder vers le public.
Ça a marché, jusqu’à ce que ça ne marche plus. Les portables à la maison ont commencé à interroger les recursors internes via un tunnel VPN non encore établi, ou via un chemin réseau filtrant les fragments UDP. Les requêtes DNS time‑out, puis retentent en TCP, puis déclenchent un comportement de repli lent dans les applications. Les pages se chargeaient comme en 2003.
L’équipe ops a ajouté une couche : dnsmasq local sur les portables pour cacher plus agressivement. Ça a introduit un autre cache, d’autres timeouts, et un nouveau mode de panne : quand le VPN se connectait, dnsmasq ne reprenait pas toujours les serveurs internes rapidement. Les utilisateurs voyaient « ça marche après un reboot », équivalent IT de « éteins et rallume », sauf que maintenant c’est une politique.
Ils ont finalement rollbacké et utilisé un routage par domaine : suffixes internes vers DNS interne uniquement quand le VPN est actif. La latence s’est améliorée car ils ont supprimé le chemin mal routé. La leçon : l’« optimisation » des performances DNS déplace souvent simplement le temps d’attente vers un endroit moins visible.
Histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Dans une entreprise réglementée, les changements DNS nécessitaient une demande de changement avec une checklist pré et post‑flight. Tout le monde se plaignait que c’était lent. Puis un incident split-horizon survint pendant une fusion : deux réseaux, deux jeux de zones internes, et un pont VPN.
La pratique salvatrice n’était pas une techno fancy. C’était que l’équipe DNS disposait d’un hôte « canari » standard en dehors de chaque réseau qui interrogeait en continu une petite liste de noms internes et externes contre les résolveurs prévus. Il interrogeait aussi depuis l’intérieur pour garantir que les views internes restaient internes. Chaque vérification enregistrait NXDOMAIN/SERVFAIL et TTL.
Quand l’incident a commencé, ils n’ont pas discuté de savoir si le DNS était cassé. Le canari a montré : des noms internes étaient interrogés sur des résolveurs publics depuis des endpoints connectés au VPN. Cela a réduit la recherche au routage client ou à la configuration VPN, pas aux serveurs faisant autorité.
L’équipe VPN avait poussé un nouveau profil qui retirait les domaines de routage et les remplaçait par des domaines de recherche. Le canari l’a détecté en quelques minutes. Le rollback a été rapide car ils avaient le profil précédent archivé et testé. Tout le monde est retourné à se plaindre de la bureaucratie, mais la production est restée vivante.
Checklists / plan étape par étape (faire ceci, dans cet ordre)
Étape 0 — Décidez ce que « interne » signifie
- Listez les suffixes DNS internes (ex :
internal.example,svc.internal.example). - Interdisez ou échelonnez la suppression des TLD privés qui ne sont pas des sous‑domaines d’un domaine que vous possédez (ex :
.internal,.corp), sauf si vous avez une politique documentée. - Décidez si vous supportez les noms courts. Si oui, définissez le comportement des domaines de recherche et acceptez les collisions.
Étape 1 — Valider les données faisant autorité
- Interrogez SOA et NS pour chaque zone interne depuis l’intérieur du réseau.
- Confirmez les TTL et les TTL négatifs.
- Si le split est sur l’autorité via des views, testez les deux views depuis des sous‑réseaux connus.
Étape 2 — Valider le comportement récursif
- Assurez‑vous que les résolveurs internes peuvent joindre de façon fiable les serveurs faisant autorité internes.
- Assurez‑vous que les résolveurs internes ne deviennent pas des résolveurs ouverts accidentels.
- Configurez le forwarding conditionnel/zones stub explicitement, pas via de la magie non documentée.
Étape 3 — Corriger les clients à la manière Debian 13
- Utilisez
resolvectl statuspour confirmer les serveurs DNS par lien. - Assurez‑vous que les liens VPN transportent des domaines de routage (
~suffix), pas seulement des domaines de recherche. - Gardez un DNS global pour la résolution publique ; ne routez pas tout via le VPN sauf si la politique l’exige.
Étape 4 — Mettre en place des garde‑fous
- Monitoring depuis plusieurs points de vue (interne, externe, VPN) avec des résolveurs explicites.
- Alerte sur les pics soudains de NXDOMAIN pour les suffixes internes.
- Runbooks qui commencent par « quel chemin résolveur est utilisé », pas par « redémarrer bind ».
Étape 5 — Communiquer la réalité du cache
- Informez les parties prenantes que les changements DNS peuvent prendre jusqu’à TTL + TTL négatif pour se propager dans les caches.
- Ayez une procédure contrôlée de vidage de cache pour les endpoints (pas « tout le monde redémarre »).
- Gardez des TTL raisonnables : assez bas pour le changement, assez haut pour éviter les tempêtes de requêtes.
FAQ
1) Pourquoi cela a commencé après la migration vers Debian 13 ?
Parce que la pile résolveur côté client est plus pilotée par des politiques. systemd-resolved supporte le DNS par lien et les domaines de routage, et il ne « devinera » pas que git.internal appartient à votre VPN à moins que vous ne le lui indiquiez.
2) Dois‑je désactiver systemd-resolved et revenir à un simple /etc/resolv.conf ?
Seulement si vous êtes prêt à remplacer ses capacités de routage par lien par quelque chose d’équivalent. Pour les portables et le split DNS VPN, le désactiver rend généralement les choses pires, pas meilleures.
3) Quelle est la différence entre un domaine de recherche et un domaine de routage (~domain) ?
Un domaine de recherche étend les noms courts (la requête git devient git.office.example). Un domaine de routage indique à resolved quel serveur DNS utiliser pour les noms dans ce suffixe. Les domaines de routage n’étendent pas les noms courts.
4) Pourquoi NXDOMAIN reste‑t‑il « collant » même après avoir ajouté l’enregistrement ?
Cache négatif. Les résolveurs mettent en cache « n’existe pas » selon le TTL négatif du SOA de la zone. Videz les caches pour vérifier, puis ajustez le TTL négatif si votre cadence de changements l’exige.
5) Est‑ce acceptable d’utiliser .internal ou .corp comme TLD privé ?
Ça marche jusqu’à ce que ça marche plus. La pratique la plus sûre est d’utiliser des sous‑domaines d’un domaine que vous possédez (par ex. internal.example). Les TLD privés compliquent le routage split DNS, la détection de fuite et l’interopérabilité.
6) Pourquoi dig fonctionne mais mon navigateur non ?
dig interroge exactement ce que vous lui demandez. Votre navigateur utilise le résolveur système, peut‑être DoH, ou peut être dans un namespace de conteneur. Validez avec getent et vérifiez les paramètres DoH du navigateur ainsi que les configs DNS des conteneurs.
7) Puis‑je résoudre ça en mettant les enregistrements internes dans le DNS public ?
Vous pouvez, mais c’est généralement un mauvais compromis. Vous divulguez la topologie interne, compliquez le contrôle d’accès et risquez d’exposer des services involontairement. Corrigez les domaines de routage et le forwarding conditionnel à la place.
8) Ai‑je besoin de DNSSEC pour les zones internes ?
Pas strictement, mais il faut un plan cohérent. Des résolveurs validants avec un DNSSEC interne incohérent c’est une recette pour SERVFAIL. Soit signez et gérez correctement, soit désactivez la validation pour ces zones sur le résolveur.
9) Quelle est la façon la plus propre de supporter à la fois les utilisateurs on‑prem et VPN ?
Utilisez des FQDN sous un domaine que vous contrôlez (p.ex. svc.internal.example), poussez des domaines de routage via le VPN, et gardez le DNS public séparé. Testez ensuite depuis trois points de vue : sur site, en VPN, et depuis l’extérieur.
Conclusion : prochaines étapes sans mauvaises surprises
Le split-horizon DNS échoue quand vous comptez sur des comportements implicites. La pile résolveur de Debian 13 est explicite : elle demande des domaines de routage explicites, des règles de forwarding explicites et des conventions de nommage explicites. C’est une bonne nouvelle, car cela signifie que vous pouvez rendre le comportement déterministe.
Faites ceci ensuite :
- Choisissez vos suffixes internes et standardisez‑les sous un domaine que vous contrôlez.
- Sur les postes Debian 13, vérifiez le DNS par lien et les domaines de routage avec
resolvectl status. - Corrigez les profils VPN pour pousser des domaines de routage
~internal.example, pas seulement des serveurs DNS. - Confirmez que les TTL négatifs SOA ne sabotent pas votre temps de reprise.
- Mettez en place du monitoring depuis au moins deux points de vue réseau et alertez sur la fuite de noms internes vers des résolveurs publics.
Puis arrêtez de « réparer le DNS » en codant en dur des résolveurs globaux. Ce n’est pas une réparation. C’est une nouvelle panne avec un meilleur marketing.