Je laptop zit op de VPN, dig service.internal.example antwoordt met een 10.x-adres, en curl stopt nog steeds met Could not resolve host. De tunnel is niet het probleem: hij heeft de dig-query zonder klagen vervoerd. Wat verschilt, is wie de vraag stelt. dig stuurt één query naar één server die jij aanwijst, terwijl curl door de opzoekketen van het besturingssysteem gaat, en die keten heeft eigen regels over welke server wordt gevraagd.

Twee opzoekingen voor dezelfde naam

dig is zelf een stub-resolver. Het leest /etc/resolv.conf, kiest een nameserver, stuurt één A- of AAAA-query en print het antwoord. Het slaat nsswitch volledig over en weet niets van de per-link-status die systemd-resolved of NetworkManager buiten dat bestand bijhoudt; daarom kan een dig-antwoord correct zijn en je toch niets vertellen over wat je applicatie zal zien.

Applicaties roepen getaddrinfo aan, en getaddrinfo loopt een langere keten af: de modulevolgorde van nsswitch, dan de module die de naam afhandelt, en dan de resolver die uit de huidige linkconfiguratie is opgebouwd. Op een systemd-machine gaat de query naar 127.0.0.53 en kiest systemd-resolved een upstream-server per link op basis van het routingdomein dat aan de tun-interface hangt.

Twee commando’s scheiden die lagen zonder te gissen:

getent ahosts service.internal.example
resolvectl query service.internal.example

getent gebruikt hetzelfde pad als curl, dus een getent-fout naast een werkende dig lokaliseert het probleem in het opzoekbeleid en niet in de packet-routing. resolvectl query print ook de interface en de upstream die het antwoord leverde, wat laat zien of de tunnellink of de fysieke link de beslissing heeft gewonnen.

Lees de resolver die de applicatie echt gebruikt

/etc/resolv.conf wordt op de meeste desktops gegenereerd, en een VPN-client heeft drie manieren om ermee om te gaan: een nameserver vooraan zetten, het bestand overschrijven, of zijn servers aan NetworkManager geven en de daemon het laten herschrijven. Alleen het derde patroon behoudt DNS per link, omdat de servers aan een interface gekoppeld blijven in plaats van tot één globale lijst te worden platgeslagen.

Op NetworkManager-systemen hangt de uitkomst af van de backend in /etc/NetworkManager/NetworkManager.conf:

[main]
dns=systemd-resolved

Met dns=default worden de servers van de client in willekeurige volgorde in resolv.conf samengevoegd, en een privé-opzoeking kan nog steeds naar de publieke resolver gaan. Met een backend die links begrijpt, systemd-resolved of dnsmasq, blijven de servers van de tunnel beperkt tot de tunnel en gaan alleen namen die bij zijn routingdomein passen daarheen. Die beperking is wat split DNS in de praktijk betekent, en dat is het stuk dat het vaakst ontbreekt wanneer een client beweert het te ondersteunen.

Lees de status voordat je iets aanpast:

resolvectl status
resolvectl dns tun0

Je wilt de tun-interface zien staan met een DNS-server en een routingdomein voor het privésuffix, weergegeven als ~internal.example. Geen server op de link betekent dat de client niets heeft gestuurd. Een server zonder domein betekent dat opzoekingen allemaal daarheen gaan of helemaal niet, afhankelijk van de client, en geen van beide is split DNS.

Waar nsswitch het opzoeken vroegtijdig beëindigt

Een standaardregel op een desktop ziet er zo uit:

hosts: files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns myhostname

mdns4_minimal regelt multicast-DNS en claimt elke naam die op .local eindigt. Een Kubernetes-service zoals api.infra.svc.cluster.local eindigt ook op .local. Wanneer de mDNS-module NOTFOUND meldt, beëindigt de actie [NOTFOUND=return] het hele opzoeken in plaats van door te gaan naar de dns-module, dus de naam wordt nooit naar de VPN-resolver gestuurd. dig omzeilt het bestand volledig, en dat is precies waarom de twee tools het oneens zijn.

Dat is het scherpste contrast in dit probleem, en het komt neer op één woord in een configuratiebestand dat ouder is dan je VPN. De actie wijzigen in [NOTFOUND=continue] behoudt mDNS voor printers en lokale apparaten terwijl DNS de naam kan zien. De regel vervangen door hosts: files dns werkt ook en is grover: het laat de .local-resolutie vallen voor de printer die je echt gebruikt.

Namen die in precies één netwerk leven

Privézones zijn op precies één plek autoritatief. Een AWS-naam zoals ip-10-0-4-31.eu-west-1.compute.internal wordt beantwoord via de VPC-resolver van die regio, en alleen zolang je VPN-endpoint in dezelfde regio zit als de resource. Verbind met een endpoint in Frankfurt, vraag een compute.internal-naam uit Ierland op, en geen enkele lokale instelling helpt: de zone met het record is daarvandaan niet bereikbaar.

Zoekdomeinen geven het spiegelbeeld. Een kale service wordt aangevuld met het suffix dat de resolver van toepassing acht, en met een VPN-link kan het lokale domein winnen van het bedrijfsdomein. Vraag op met een punt aan het eind om het gissen te verwijderen, getent ahosts service.internal.example., en als dat werkt terwijl de korte naam faalt, dan is het zoekdomein op de tunnel de oplossing, niet het serveradres.

Beheerde clients voegen een derde laag toe boven de resolver van het besturingssysteem. Een Windows-VPN-profiel kan zijn eigen naamresolutiebeleid, regels per app en een NRPT-tabel meedragen die ook geldt als de tunnel uit staat. Applicaties met een eigen DNS-cache, gangbaar in .NET- en Java-runtimes, houden een verouderde fout lang vast nadat de keten is gerepareerd. Apple-clients configureren split DNS via match-domeinen in de DNS-instellingen in plaats van via resolv.conf, en Chromium of Firefox in secure-DNS-modus kunnen de systeemresolver omzeilen voor publieke namen. Microsofts naslagwerk over VPN-naamresolutie beschrijft de Windows-kant van die beleidslaag.

Bepaal welke laag je moet repareren

Werk van het symptoom naar de laag in plaats van fixes op volgorde te proberen:

SymptomWhat it meansCheck first
dig answers, getent failslookup chain, not the packet pathnsswitch line and resolvectl status
Timeout rather than NXDOMAINprivate resolver unreachable through the tunnelresolvectl dns tun0, route to that address
NXDOMAINan authority answered and does not know the namezone owner, endpoint region
FQDN works, short name failssearch suffix mismatchresolvectl domain on the tun link
getent works, app failsapp cache or its own resolverrestart the app, check secure DNS

Twee rijen dragen het meeste gewicht. Een timeout is een bereikbaarheidsprobleem: de privé-resolver antwoordt niet door de tunnel, dus controleer de route naar zijn adres en of de DNS-server op de link degene is die je denkt. NXDOMAIN is een autoriteitsprobleem: iets heeft geantwoord en kent de naam niet, wat wijst op de zone, de regio of een resolver die de query nooit had mogen ontvangen.

De laatste rij is degene die mensen missen omdat beide helften op succes lijken. Een korte naam die anders resolvet dan de FQDN is een suffix-mismatch, en de tunnel opnieuw verbinden verandert niet welk suffix de client stuurt.

Leg de status vast voordat je een configuratiebestand aanpast, zodat je kunt zien of de wijziging iets deed:

resolvectl status > before.txt
getent ahosts service.internal.example >> before.txt

Voer de controles in deze volgorde uit en stop bij de eerste die faalt. getent en resolvectl query moeten overeenkomen; als getent faalt terwijl dig antwoordt, blijf dan in de opzoekketen en laat routing met rust. resolvectl status moet een DNS-server en een routingdomein op de tun-interface tonen. nsswitch mag bij NOTFOUND niet vroegtijdig terugkeren voor het suffix dat je test. De naam moet autoritatief zijn in het netwerk waaraan je endpoint hangt, getest met een punt aan het eind. De applicatie mag geen eigen cache of een secure-DNS-override hebben.

Repareer alleen de eerste laag die faalt en voer daarna dezelfde commando’s opnieuw uit. Twee wijzigingen tegelijk laten je niet zeggen welke ertoe deed, en de volgende storing begint het onderzoek weer bij nul.

Gerelateerde artikelen