Pourquoi resolv.conf affiche le DNS de votre VPN alors que les requêtes l'ignorent
Sur un hôte avec resolved, le fichier est une vue, pas l'état. Suivez le stub, les serveurs par lien et la ligne nsswitch pour voir ce qu'une requête utilise vraiment.
Rien dans les journaux ne semble anormal : l'interface est active, le résolveur annonce le serveur DNS du VPN et le fichier sur disque le liste. Pourtant, un nom d'hôte interne se résout toujours via le routeur local. Ces trois affirmations ne se contredisent pas, car le fichier, l'état par lien et le chemin de la requête sont des objets différents qui partagent par hasard un nom. Ce qui suit est le chemin qu'une requête emprunte réellement sur un hôte avec systemd-resolved.
Trois objets nommés resolv.conf
Sur un hôte avec resolved, /etc/resolv.conf est généralement un lien symbolique vers /run/systemd/resolve/stub-resolv.conf. Cette cible contient l'adresse d'un écouteur stub local, 127.0.0.53, ainsi que les domaines de recherche calculés par resolved. C'est une vue de l'état, pas l'état lui-même.
L'état vit dans la configuration par lien de resolved, et c'est pourquoi resolvectl status peut contredire le fichier et avoir raison malgré tout. Chaque lien porte ses propres serveurs et ses propres domaines de routage, et le stub consulte ces données quand une requête arrive. Le manuel de systemd-resolved décrit les fichiers générés et le modèle par lien ; la séparation entre vue et état est la partie à retenir. Modifier la cible du lien symbolique change ce que lisent les clients traditionnels, pas ce que sait le résolveur.
Un troisième objet, /run/systemd/resolve/resolv.conf, aplatit chaque lien en une seule liste de serveurs. Un programme pointé vers ce fichier envoie les requêtes directement aux serveurs listés et contourne le stub, si bien que la politique par lien ne s'applique plus à lui. Deux programmes sur la même machine peuvent donc répondre différemment au même nom, et aucun des deux n'est cassé.
Suivez une requête à travers NSS
glibc lit /etc/nsswitch.conf pour décider qui répond à un nom. Une ligne hosts contenant resolve achemine la question vers nss-resolve, qui interroge resolved via D-Bus et reçoit la réponse par lien. Une ligne avec seulement files dns envoie la requête à l'adresse que détient /etc/resolv.conf.
Cette seule ligne explique la plupart des désaccords entre un navigateur et dig. dig lit resolv.conf directement et ne touche jamais à NSS, il teste donc le stub ou un serveur amont plutôt que le chemin utilisé par l'application. getent hosts internal.example passe par NSS, et comparer sa réponse à celle de dig est la première mesure utile.
Les applications qui embarquent leur propre résolveur, ou qui parlent DNS sur HTTPS avec un point de terminaison fixe, contournent les deux chemins. Rien dans resolv.conf ni dans l'état de resolved ne les contraint.
Vérifiez qui possède le fichier et vers quoi il pointe
Lisez la chaîne de liens avant de changer quoi que ce soit, car la propriété décide quel fichier fait autorité. Chaque commande ci-dessous ne fait que lire l'état.
stat -c '%N' /etc/resolv.conf
resolvectl status --no-pager
grep '^hosts' /etc/nsswitch.conf
ls -l /run/systemd/resolve/
La première ligne imprime le lien et sa cible ensemble, et un fichier ordinaire possédé par un script VPN signifie que le résolveur ne traite jamais cette écriture comme une entrée. La sortie d'état liste les serveurs par lien ainsi que les domaines de routage qui décident où va chaque requête. La ligne nsswitch dit si le stub est consulté du tout, donc une adresse stub dans le fichier sans resolve à cet endroit signifie que les requêtes atteignent l'écouteur local tandis que rien ne gère la séparation. Cette dernière liste compte aussi, car un hôte sans stub-resolv.conf n'exécute pas la disposition décrite ici.
Si la chaîne se termine sur un fichier géré par un autre paquet, le client VPN et ce paquet sont les deux propriétaires à réconcilier. Remplacer le lien jette le travail d'un propriétaire et laisse celui de l'autre en place.
Réparez chez le propriétaire du lien
Une connexion VPN devrait enregistrer ses serveurs et ses domaines sur son propre lien, puis les retirer à la déconnexion. L'intégration resolved de NetworkManager et l'assistant propre à un client fonctionnent ainsi ; un script qui ne fait que réécrire resolv.conf, non.
Sur un lien qui existe déjà, la configuration à l'exécution montre le mécanisme. resolvectl dns tun0 10.40.0.53 attache un serveur, et resolvectl domain tun0 '~corp.example' attache un domaine de routage. Le tilde est le caractère porteur : il confine les requêtes de ce suffixe à ce lien et laisse le reste tranquille, alors que la même valeur sans lui devient un domaine de recherche et la séparation s'évapore. Ces adresses sont des exemples de laboratoire, pas une configuration persistante. Confirmez avec resolvectl query host.corp.example et lisez quel lien a répondu.
Un serveur enregistré a encore besoin d'une route. Quand la requête atteint le bon lien puis expire, la faute est dans l'accessibilité à travers le tunnel, pas dans la propriété du résolveur.
À quoi ressemble un état qui fonctionne
Un résultat sain est cohérent dans les trois vues : les serveurs par lien correspondent à ce que le VPN a distribué, les domaines de routage ne couvrent que les suffixes internes, et l'application qui compte résout un nom interne tandis qu'un nom public suit toujours le chemin local. Une requête réussie prouve moins que cet accord.
Déconnectez ensuite et répétez les deux requêtes. Un client qui laisse ses domaines enregistrés après le démontage produit une machine qui échoue aux recherches internes des heures plus tard, sans tunnel ouvert et sans cause évidente.