Android-DNS correct lezen: per netwerk, niet in resolv.conf
Je Android-app resolvet via een resolver per netwerk, niet via de resolv.conf die je net hebt gelezen. Inspecteer eerst LinkProperties, socketbindingen en Private DNS.
Je krijgt een shell op het apparaat, leest /etc/resolv.conf of diept een oude net.dns1-waarde op, en je krijgt één adres. De app voor je resolvet via iets heel anders. Op een huidige Android-versie is de resolver per netwerk: het systeem houdt een beleid bij voor elk Network en past dat toe op de socket die het verzoek heeft gedaan. Het bestand en de oude eigenschap zijn overblijfselen, en geen van beide beschrijft het pad dat je query werkelijk heeft gevolgd.
Die kloof wordt zichtbaar bij de uitrol van split-tunnel en Private DNS, waar twee apps op dezelfde telefoon legitiem verschillende antwoorden kunnen zien. Deze uitleg begint bij de verkeerde of ontbrekende opzoeking en werkt terug naar de configuratie die er eigenaar van is.
De resolver hoort bij een netwerk, niet bij het apparaat
Naamresolutie op Android 9 en later loopt via een systeemresolver die uitgaat van het Network waaraan een socket is gebonden. Elk netwerk draagt zijn eigen LinkProperties, en die eigenschappen bevatten de DNS-serverlijst, de zoekdomeinen en de Private DNS-status voor dat netwerk alleen. Niets in dat model komt overeen met één apparaatbrede lijst die je uit een bestand kunt lezen.
Een VPN maakt de splitsing duidelijk. Wanneer de tunnel omhoog komt, levert de VPN-app DNS-servers die op het eigen netwerk van de VPN zijn geregistreerd, zodat apps die erdoor worden gerouteerd tegen die servers resolven terwijl uitgesloten apps het onderliggende netwerk blijven gebruiken. Eén apparaat, twee antwoordpaden, geen tegenspraak.
De momentopname vanuit de betrokken app lezen
Vraag het de app zelf. Een methode met een Context en de normale android.permission.ACCESS_NETWORK_STATE-toestemming kan de momentopname lezen zonder systeemeigenschappen aan te raken:
val cm = getSystemService(ConnectivityManager::class.java) ?: return
val network = cm.activeNetwork
val links = network?.let { cm.getLinkProperties(it) }
val dnsServers = links?.dnsServers
val privateDnsActive = links?.isPrivateDnsActive
val privateDnsName = links?.privateDnsServerNameactiveNetwork is de standaard voor je proces, niet het enige netwerk op het apparaat, en kan null zijn wanneer de aanroeper geen bruikbare standaard heeft. Een geblokkeerde VPN-standaard is de gebruikelijke reden. isPrivateDnsActive vertelt je of query’s op dat netwerk via TLS naar een benoemde resolver gaan, wat verandert wat een gewone UDP 53-probe je laat zien. Het API-oppervlak is gedocumenteerd onder LinkProperties, en elke waarde erin is een momentopname van configuratie en geen bewijs dat een bepaalde query een bepaalde server heeft bereikt.
Wanneer het standaardnetwerk niet het netwerk is dat het werk doet
De momentopname antwoordt alleen voor de standaard. Een app kan zijn proces of één socket aan een ander netwerk binden, en dan volgt de opzoeking die keuze terwijl je diagnose nog steeds de standaard meldt. Een browser met een eigen DNS-over-HTTPS-resolver raadpleegt de systeemlijst helemaal niet, dus zijn antwoorden zeggen niets over de rest van het apparaat. Een werkprofiel-app draait onder een aparte gebruiker met eigen beleid. Controleer de binding voordat je de servers de schuld geeft.
VPN-integratie is de andere helft. Een dhcp-option DNS-regel in een OpenVPN-profiel ontvangen en dat adres op het Android-VPN-netwerk registreren zijn twee afzonderlijke stappen, en een ontbrekende route naar de server faalt net zo stil als een verkeerd adres. Vanaf een ontwikkelmachine drukt adb shell dumpsys connectivity dezelfde linkeigenschappen per netwerk af wanneer de build het toestaat, wat de lijst bevestigt zonder code te schrijven.
Configuratie vergelijken met één opzoeking die je mag doen
Kies een naam die binnen de privénaamruimte moet resolven en een publieke naam, en noteer beide resultaten naast de momentopname: netwerk, geconfigureerde servers, Private DNS-status en de exacte fouttekst. Een directe query naar een benoemde server bewijst dat die server jou antwoordt; het bewijst niet dat gewone applicatieresolutie hem heeft gekozen. Een gecachet antwoord is geen verse test, dus leeg de cache of gebruik een naam die vandaag nog niemand heeft opgevraagd.
Als de resolver alleen clients van een bepaald toegangsnetwerk bedient, kan wat hij na VPN-egress ziet de uitkomst bepalen, en dat is een beleidsvraag voor wie hem beheert, geen bewijs dat providers UDP 53 blokkeren. Waar Private DNS actief is, kan een niet-gekwalificeerde hostnaam ontwerpmatig worden geweigerd terwijl dezelfde server publieke namen normaal beantwoordt. De eigenschappen lezen vertelt je welke modus geldt, zodat je ophoudt met gissen naar packetfilters.
De laag repareren die eigenaar is van de mismatch
Werk omhoog in plaats van globale status te bewerken. Is de app aan het verkeerde netwerk gebonden, repareer de binding. Levert het profiel de verkeerde DNS-optie, repareer het profiel. Is Private DNS strikt op een netwerk waarvan de resolver je privézone niet kan bedienen, dan is dat een beslissing van devicebeheer en geen reden om de functie overal uit te zetten.
Publieke resolvers in een DHCP-hook of systeemeigenschap forceren is de reparatie die maar incidenten blijft veroorzaken: privénamen resolven niet meer, de wijziging overleeft de VPN-sessie, en de volgende persoon op het apparaat vindt een verouderd adres zonder eigenaar. Houd de wijziging binnen de VPN, de app of de beheerslaag die eigenaar is van de mismatch, en lees daarna de momentopname opnieuw om te bevestigen dat de serverlijst echt is veranderd.
Wat je uiteindelijk wilt, is één app-verzoek gekoppeld aan één netwerk, resolverlijst en Private DNS-status. Zodra je die keten kunt laten zien, is een bekend adres in een bestand geen bewijs meer voor iets.