I'll try to explain my understanding of DNS on Android. It will help you troubleshoot related problems and serve me as future notes.
Domain Name Resolver has traditionally been a part of OS's C library (commonly called libc).
GNU libc (which is most common on Linux distros) implements a complicated name resolution mechanism named NSS which can prioritize in what order database services like LDAP / NIS, local
DNS have to be used in order to resolve host / domain names. DNS resolver then reads
/etc/resolv.conf to get the name server address which is to be queried.
GNU libc, Android's
Bionic libc (which depends on
netd for DNS) doesn't regard
/etc/resolv.conf unless ANDROID_CHANGES build variable is defined (only when libc++ is built as static library?). However /etc/hosts entries are valued by Android's resolver when resolving names.
But if you are using a statically compiled / linked binary with some other - such as
diet - libc, that must be reading
/etc/resolv.conf to get Domain Name Server's IP address. Busybox ping is a common example.
Within Android's Java runtime environment, either a DHCP provided or manually set or hard coded DNS server is used. Properties
net.dns2 are set with values of DNS servers received from DHCP server (on Wi-Fi or Mobile Data) by
ConnectivityService and then forwarded to
netd. Starting with Android Oreo, these properties are no longer available through Android APIs, but only readable as
shell - once removed then re-added. So changing properties
setprop only affects the programs (running with UID
2000) that explicitly read these properties. One such example is meefik's busybox. Also there were some other DNS related properties set in good old days of
dhcpcd which was the DHCP client up to Lollipop.
See some more details in How does Android OS do DNS name resolution?.
DNS queries are cached on Android by
NSCD does on Linux distros to speed up name resolution. Android has a commandline tool
ndc that could clear DNS cache but the command was removed in Android 7. Now it's possible to bypass caching by setting environment variable
ANDROID_DNS_MODE which prevents Bionic libc from proxying back to
netd service for cache lookup. See How to modify environment variables like PATH at startup? to set the variable globally.
How to set DNS
As mentioned earlier, previously different methods used to change default Domain Name Servers on Android don't seem to work now. Setting name servers and/or search domain name through commandline:
~$ ndc resolver setnetdns <network_id> <domain> <DNS>
* Commands syntax is a bit different on older versions
ndc tether dns set works for
dnsmasq. To get current network ID:
~$ dumpsys netd | grep 'Default network'
Clear set DNS servers:
~# ndc resolver clearnetdns <network_id>
However some apps may sometimes bypass Android's name resolution mechanism trying directly reach out to some nameserver, I have observed WhatsApp. Or the hard-coded value is used (may be when router is advertising the IPv6 DNS server). The only certain working option is
~# iptables -t nat -I OUTPUT -p udp --dport 53 -j DNAT --to 22.214.171.124:53
DNS servers can also be configured in Settings for Wi-Fi network. Android 9+ supports Private DNS / DNS over TLS (DoT) which sends encrypted DNS queries on port 853 as standardized in RFC7858.
Non-root solution which works for both Wi-Fi and Mobile data is to use VPN apps like Virtual Hosts or Personal DNS Filter which intercept DNS traffic and makes queries to configured upstream DNS server.
To test what name server is being used by
netd services in Java runtime:
~# dumpsys connectivity | grep CONNECTED | grep -o 'DnsAddresses: \[[^ ]*'
~# dumpsys netd | grep -A2 'DNS servers:'
And those used by tethering forwarding DNS server:
~# ndc tether dns list
~# logcat -d -s TetherController,dnsmasq | grep -E 'update_dns|nameserver'
To see where the DNS queries are going, you may capture traffic destined for port 53:
~# iptables -I OUTPUT -m udp -p udp --dport 53 -j LOG --log-prefix 'DNS_QUERIES '
~# iptables -I OUTPUT -m tcp -p tcp --dport 53 -j LOG --log-prefix 'DNS_QUERIES '
~# dmesg -w | grep 'DNS_QUERIES'
~# tcpdump -n -i any port 53
If the queries are not going to desired destination, there is some trouble with your DNS configuration, particularly if you use VPN. Please note that
tcpdump (capturing on port
53) won't work with Private DNS (port
853). Dynamic nature of RPDB and routing tables (rt_tables keeps on changing, main table is not used) accompanied by fwmark (SO_MARK) make things complicated for manual control.
So if above mentioned steps don't work, there's nothing much you can do to fix DNS issues on Android devices. A Factory Reset or re-flashing of ROM may resolve the problem.
Additionally, if a local DNS server is provided by DHCP (server on Wi-Fi router), you may check if that's really listening on port 53:
~# nmap -e eth0 -sUT -p53 --script=dns-recursion 192.168.1.245