32

I'm trying to get hold of the addresses to the currently used DNS servers in my application, either I'm connected thru Wifi or mobile. The DhcpInfo object should provide this but how can I get hold of a valid DhcpInfo object?

Mikel
  • 24,855
  • 8
  • 65
  • 66
John
  • 321
  • 1
  • 3
  • 3

8 Answers8

27

Calling for the getRuntime().exec can hang your application.

android.net.NetworkUtils.runDhcp() cause unnecessary network requests.

So I prefer to do this:

Class<?> SystemProperties = Class.forName("android.os.SystemProperties");
Method method = SystemProperties.getMethod("get", new Class[] { String.class });
ArrayList<String> servers = new ArrayList<String>();
for (String name : new String[] { "net.dns1", "net.dns2", "net.dns3", "net.dns4", }) {
    String value = (String) method.invoke(null, name);
    if (value != null && !"".equals(value) && !servers.contains(value))
        servers.add(value);
}
Community
  • 1
  • 1
A-IV
  • 2,555
  • 2
  • 21
  • 17
  • If you're working in the AOSP (system app or library), you can directly import `android.os.SystemProperties` and use `public static String get(String key, String def)` – Arnaud Courtecuisse Feb 17 '15 at 11:12
  • 4
    Seems that this is no longer allowed in Android Oreo. Any updates here? – atsakiridis Dec 04 '17 at 11:51
  • 1
    yeah this is no longer allowed in oreo – Hades Dec 06 '17 at 00:47
  • 1
    I can confirm this method is NOT working anymore in Android 8.0 Official Android documentation state this very clearly in the article Android 8.0 Behavior Changes. The system properties net.dns1, net.dns2, net.dns3, and net.dns4 are no longer available, a change that improves privacy on the platform. https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri – Grigore Madalin Feb 24 '18 at 17:25
  • it works for `mDNS`? – Varun Jul 04 '22 at 13:38
21

Unfortunatelly most solutions presented are NOT working anymore in Android 8.0

Official Android documentation state this very clearly in the article Android 8.0 Behavior Changes. The system properties net.dns1, net.dns2, net.dns3, and net.dns4 are no longer available, a change that improves privacy on the platform.

https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri

Dnsjava library is also afected and the detection methods used in dnsjava are not aware of Oreo changes.

Varun Anand solution works on Oreo but have an weakness in not handling connection with default routes. Because of this the result maybe poisoned with invalid DNS servers comming first into the result and the caller may spend a lot of time iterating the list and trying to connect to unreacheble DNS servers. This was fixed into my solution. Another problem with Varun Anand solution is this only works for API 21 and above. But i must say it was gold mine for me to write my own solution. So thank's!

For your convenience i provided a full DNS servers detector class you can use that works on any android version. Full comments are included to answer to why and how.

/**
 * DNS servers detector
 *
 * IMPORTANT: don't cache the result.
 *
 * Or if you want to cache the result make sure you invalidate the cache
 * on any network change.
 *
 * It is always better to use a new instance of the detector when you need
 * current DNS servers otherwise you may get into troubles because of invalid/changed
 * DNS servers.
 *
 * This class combines various methods and solutions from:
 * Dnsjava http://www.xbill.org/dnsjava/
 * Minidns https://github.com/MiniDNS/minidns
 *
 * Unfortunately both libraries are not aware of Orero changes so new method was added to fix this.
 *
 * Created by Madalin Grigore-Enescu on 2/24/18.
 */

public class DnsServersDetector {

    private static final String TAG = "DnsServersDetector";

    /**
     * Holds some default DNS servers used in case all DNS servers detection methods fail.
     * Can be set to null if you want caller to fail in this situation.
     */
    private static final String[] FACTORY_DNS_SERVERS = {
            "8.8.8.8",
            "8.8.4.4"
    };

    /**
     * Properties delimiter used in exec method of DNS servers detection
     */
    private static final String METHOD_EXEC_PROP_DELIM = "]: [";

    /**
     * Holds context this was created under
     */
    private Context context;

    //region - public //////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Constructor
     */
    public DnsServersDetector(Context context) {

        this.context = context;

    }

    /**
     * Returns android DNS servers used for current connected network
     * @return Dns servers array
     */
    public String [] getServers() {

        // Will hold the consecutive result
        String[] result;

        // METHOD 1: old deprecated system properties
        result = getServersMethodSystemProperties();
        if (result != null && result.length > 0) {

            return result;

        }

        // METHOD 2 - use connectivity manager
        result = getServersMethodConnectivityManager();
        if (result != null && result.length > 0) {

            return result;

        }

        // LAST METHOD: detect android DNS servers by executing getprop string command in a separate process
        // This method fortunately works in Oreo too but many people may want to avoid exec
        // so it's used only as a failsafe scenario
        result = getServersMethodExec();
        if (result != null && result.length > 0) {

            return result;

        }

        // Fall back on factory DNS servers
        return FACTORY_DNS_SERVERS;

    }

    //endregion

    //region - private /////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * Detect android DNS servers by using connectivity manager
     *
     * This method is working in android LOLLIPOP or later
     *
     * @return Dns servers array
     */
    private String [] getServersMethodConnectivityManager() {

        // This code only works on LOLLIPOP and higher
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            try {

                ArrayList<String> priorityServersArrayList  = new ArrayList<>();
                ArrayList<String> serversArrayList          = new ArrayList<>();

                ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE);
                if (connectivityManager != null) {

                    // Iterate all networks
                    // Notice that android LOLLIPOP or higher allow iterating multiple connected networks of SAME type
                    for (Network network : connectivityManager.getAllNetworks()) {

                        NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
                        if (networkInfo.isConnected()) {

                            LinkProperties linkProperties    = connectivityManager.getLinkProperties(network);
                            List<InetAddress> dnsServersList = linkProperties.getDnsServers();

                            // Prioritize the DNS servers for link which have a default route
                            if (linkPropertiesHasDefaultRoute(linkProperties)) {

                                for (InetAddress element: dnsServersList) {

                                    String dnsHost = element.getHostAddress();
                                    priorityServersArrayList.add(dnsHost);

                                }

                            } else {

                                for (InetAddress element: dnsServersList) {

                                    String dnsHost = element.getHostAddress();
                                    serversArrayList.add(dnsHost);

                                }

                            }

                        }

                    }

                }

                // Append secondary arrays only if priority is empty
                if (priorityServersArrayList.isEmpty()) {

                    priorityServersArrayList.addAll(serversArrayList);

                }

                // Stop here if we have at least one DNS server
                if (priorityServersArrayList.size() > 0) {

                    return priorityServersArrayList.toArray(new String[0]);

                }

            } catch (Exception ex) {

                Log.d(TAG, "Exception detecting DNS servers using ConnectivityManager method", ex);

            }

        }

        // Failure
        return null;

    }

    /**
     * Detect android DNS servers by using old deprecated system properties
     *
     * This method is NOT working anymore in Android 8.0
     * Official Android documentation state this in the article Android 8.0 Behavior Changes.
     * The system properties net.dns1, net.dns2, net.dns3, and net.dns4 are no longer available,
     * a change that improves privacy on the platform.
     *
     * https://developer.android.com/about/versions/oreo/android-8.0-changes.html#o-pri
     * @return Dns servers array
     */
    private String [] getServersMethodSystemProperties() {


        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

            // This originally looked for all lines containing .dns; but
            // http://code.google.com/p/android/issues/detail?id=2207#c73
            // indicates that net.dns* should always be the active nameservers, so
            // we use those.
            final String re1 = "^\\d+(\\.\\d+){3}$";
            final String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
            ArrayList<String> serversArrayList = new ArrayList<>();
            try {

                Class SystemProperties = Class.forName("android.os.SystemProperties");
                Method method = SystemProperties.getMethod("get", new Class[]{String.class});
                final String[] netdns = new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
                for (int i = 0; i < netdns.length; i++) {

                    Object[] args = new Object[]{netdns[i]};
                    String v = (String) method.invoke(null, args);
                    if (v != null && (v.matches(re1) || v.matches(re2)) && !serversArrayList.contains(v)) {
                        serversArrayList.add(v);
                    }

                }

                // Stop here if we have at least one DNS server
                if (serversArrayList.size() > 0) {

                    return serversArrayList.toArray(new String[0]);

                }

            } catch (Exception ex) {

                Log.d(TAG, "Exception detecting DNS servers using SystemProperties method", ex);

            }

        }

        // Failed
        return null;

    }

    /**
     * Detect android DNS servers by executing getprop string command in a separate process
     *
     * Notice there is an android bug when Runtime.exec() hangs without providing a Process object.
     * This problem is fixed in Jelly Bean (Android 4.1) but not in ICS (4.0.4) and probably it will never be fixed in ICS.
     * https://stackoverflow.com/questions/8688382/runtime-exec-bug-hangs-without-providing-a-process-object/11362081
     *
     * @return Dns servers array
     */
    private String [] getServersMethodExec() {

        // We are on the safe side and avoid any bug
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {

            try {

                Process process = Runtime.getRuntime().exec("getprop");
                InputStream inputStream = process.getInputStream();
                LineNumberReader lineNumberReader = new LineNumberReader(new InputStreamReader(inputStream));
                Set<String> serversSet = methodExecParseProps(lineNumberReader);
                if (serversSet != null && serversSet.size() > 0) {

                    return serversSet.toArray(new String[0]);

                }

            } catch (Exception ex) {

                Log.d(TAG, "Exception in getServersMethodExec", ex);

            }

        }

        // Failed
        return null;

    }

    /**
     * Parse properties produced by executing getprop command
     * @param lineNumberReader
     * @return Set of parsed properties
     * @throws Exception
     */
    private Set<String> methodExecParseProps(BufferedReader lineNumberReader) throws Exception {

        String line;
        Set<String> serversSet = new HashSet<String>(10);

        while ((line = lineNumberReader.readLine()) != null) {
            int split = line.indexOf(METHOD_EXEC_PROP_DELIM);
            if (split == -1) {
                continue;
            }
            String property = line.substring(1, split);

            int valueStart  = split + METHOD_EXEC_PROP_DELIM.length();
            int valueEnd    = line.length() - 1;
            if (valueEnd < valueStart) {

                // This can happen if a newline sneaks in as the first character of the property value. For example
                // "[propName]: [\n…]".
                Log.d(TAG, "Malformed property detected: \"" + line + '"');
                continue;

            }

            String value = line.substring(valueStart, valueEnd);

            if (value.isEmpty()) {

                continue;

            }

            if (property.endsWith(".dns") || property.endsWith(".dns1") ||
                    property.endsWith(".dns2") || property.endsWith(".dns3") ||
                    property.endsWith(".dns4")) {

                // normalize the address
                InetAddress ip = InetAddress.getByName(value);
                if (ip == null) continue;
                value = ip.getHostAddress();

                if (value == null) continue;
                if (value.length() == 0) continue;

                serversSet.add(value);

            }

        }

        return serversSet;

    }

    /**
     * Returns true if the specified link properties have any default route
     * @param linkProperties
     * @return true if the specified link properties have default route or false otherwise
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private boolean linkPropertiesHasDefaultRoute(LinkProperties linkProperties) {

        for (RouteInfo route : linkProperties.getRoutes()) {
            if (route.isDefaultRoute()) {
                return true;
            }
        }
        return false;

    }

    //endregion

}
Grigore Madalin
  • 1,195
  • 11
  • 15
  • 1
    This saved me. For anyone in future, using dnsjava, call this routine in ResolverConfig.findAndroid, add the results to lserver list – urSus Oct 17 '18 at 02:53
  • This will always return all DNS servers of all active connections. If you are on a wifi network and the cellular connection is still active, it will return the DNS servers of both connections, however only those of the wifi connection will work. – Chrissi Jul 01 '19 at 11:02
  • How to use the class? – zeleven Nov 20 '19 at 06:24
  • connectivityManager.getNetworkInfo is deprecated. I try to find solution using NetworkCallbacks to get dns. – Manikandan Mar 06 '20 at 07:24
  • @Manikandan if you do it please come with the solution so I can edit my answer including your research. I will also research what you wrote here and try to update my code if necessary... – Grigore Madalin Apr 24 '20 at 10:07
  • `getprop` doesn't return `.dns` properties anymore. Checked on Android 10 – Hsilgos Aug 19 '23 at 16:10
13

android.net.ConnectivityManager will deliver you an array of NetworkInfo's using getAllNetworkInfo(). Then use android.net.NetworkUtils.runDhcp() to get a DhcpInfo for any given network interface - the DhcpInfo structure has the IP address for dns1 and dns2 for that interface (which are integer values representing the IP address).

In case you are wondering how the hell you are going to transform the integer into an IP address, you can do this:

/**
* Convert int IP adress to String 
* cf. http://teneo.wordpress.com/2008/12/23/java-ip-address-to-integer-and-back/
*/
private String intToIp(int i) {
    return ( i & 0xFF) + "." +
        (( i >> 8 ) & 0xFF) + "." +
        (( i >> 16 ) & 0xFF) + "." +
        (( i >> 24 ) & 0xFF);
}

Edit

You can also get a DchcpInfo object by doing something like this:

WiFiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE); 
DhcpInfo info = wifi.getDhcpInfo();
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 1
    But isn´t android.net.NetworkUtils.runDhcp() a native function? How do I access it? – John Jun 18 '10 at 13:59
  • 8
    Ok, getting the DhcpInfo from Wifi is straight forward but how do you do it for mobile? (UMTS etc.) Do I have to load some native library to get access to android.net.NetworkUtils.runDhcp() ? – John Jun 18 '10 at 14:14
  • @John - I've posted a native solution that works for mobile networks. Take a look. – Grimmace Sep 20 '12 at 13:56
11

Following works for API 21 and above. It returns correct dns servers for both WiFi and Cellular interfaces. I've verified the values returned with shell utility 'getprop'

ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
    for (Network network : connectivityManager.getAllNetworks()) {
        NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
        if (networkInfo.isConnected()) {
            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
            Log.d("DnsInfo", "iface = " + linkProperties.getInterfaceName());
            Log.d("DnsInfo", "dns = " + linkProperties.getDnsServers());
            return linkProperties.getDnsServers();
        }
    }
AllThatICode
  • 1,250
  • 13
  • 19
2

I recommend dnsjava for complex DNS use on Android. Let's see how dnsjava determines the current active DNS server for the connection. From dnsjava ResolverConfig.java:428:

/**
 * Parses the output of getprop, which is the only way to get DNS
 * info on Android. getprop might disappear in future releases, so
 * this code comes with a use-by date.
 */
private void
findAndroid() {
    // This originally looked for all lines containing .dns; but
    // http://code.google.com/p/android/issues/detail?id=2207#c73
    // indicates that net.dns* should always be the active nameservers, so
    // we use those.
    String re1 = "^\\d+(\\.\\d+){3}$";
    String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
    try { 
        ArrayList lserver = new ArrayList(); 
        ArrayList lsearch = new ArrayList(); 
        String line; 
        Process p = Runtime.getRuntime().exec("getprop"); 
        InputStream in = p.getInputStream();
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader br = new BufferedReader(isr);
        while ((line = br.readLine()) != null ) { 
            StringTokenizer t = new StringTokenizer(line, ":");
            String name = t.nextToken();
            if (name.indexOf( "net.dns" ) > -1) {
                String v = t.nextToken();
                v = v.replaceAll("[ \\[\\]]", "");
                if ((v.matches(re1) || v.matches(re2)) &&
                    !lserver.contains(v))
                    lserver.add(v);
            }
        }
        configureFromLists(lserver, lsearch);
    } catch ( Exception e ) { 
        // ignore resolutely
    }
}
Flow
  • 23,572
  • 15
  • 99
  • 156
2

A native alternative is:

char dns1[PROP_VALUE_MAX];
__system_property_get("net.dns1", dns1);

Or better yet for a comprehensive list:

for (i = 1; i <= MAX_DNS_PROPERTIES; i++) {
    char prop_name[PROP_NAME_MAX];
    snprintf(prop_name, sizeof(prop_name), "net.dns%d", i);
    __system_property_get(prop_name, dns);
}

There are a few advantages to doing it this way:

  1. runDHCP is really slow. It can take as long as 5-10 seconds. This can cause a major hang when used incorrectly.
  2. runDCHP doesn't seem to work for 3G/4G.
  3. Since runDCHP is a hidden API it is subject to change. In fact it did change in ICS. In ICS it takes a new DhcpInfoInternal, so you'll have to create two different to support all phones.
Grimmace
  • 4,021
  • 1
  • 31
  • 25
1

first Add External JARs layoutlib.jar to your build path, the layoutlib.jar file in $SDK_PATH/platforms/android-xxx/data/, then

String dnsStr1 = android.os.SystemProperties.get("net.dns1");
String dnsStr2 = android.os.SystemProperties.get("net.dns2");

you also can see all property in adb shell with $getprop command.

c0ming
  • 3,407
  • 1
  • 21
  • 26
-2

You can use java reflection. example:

 ConnectivityManager mgr =
 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);          
 Method getLinkPropeties;
 try{
 getLinkPropeties = mgr.getClass().getMethod("getLinkProperties", int.class);
 }catch (InvocationTargetException e) {
     e.printStackTrace();
 }
zjg
  • 1
  • 2