您如何获取当前适用于 Android 的 DNS 服务器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3070144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 08:34:32  来源:igfitidea点击:

How do you get the current DNS servers for Android?

android

提问by John

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?

我正在尝试获取我的应用程序中当前使用的 DNS 服务器的地址,无论我是通过 Wifi 还是移动设备连接。DhcpInfo 对象应该提供这个,但是我怎样才能得到一个有效的 DhcpInfo 对象呢?

回答by A-IV

Calling for the getRuntime().execcan hangyour application.

调用getRuntime().exec可以挂起您的应用程序。

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

android.net.NetworkUtils.runDhcp()造成不必要的网络请求。

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);
}

回答by Cristian

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

android.net.ConnectivityManager将使用getAllNetworkInfo(). 然后使用android.net.NetworkUtils.runDhcp()得到DhcpInfo对于任何给定的网络接口-的DhcpInfo结构具有的IP地址dns1dns2该接口(它们是整数,表示的IP地址值)。

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

如果您想知道如何将整数转换为 IP 地址,您可以这样做:

/**
* 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 DchcpInfoobject by doing something like this:

您还可以DchcpInfo通过执行以下操作来获取对象:

WiFiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE); 
DhcpInfo info = wifi.getDhcpInfo();

回答by Grigore Madalin

Unfortunatelly most solutions presented are NOT working anymore in Android 8.0

不幸的是,大多数解决方案在 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.

Android 官方文档在文章 Android 8.0 Behavior Changes 中非常清楚地说明了这一点。系统属性 net.dns1、net.dns2、net.dns3 和 net.dns4 不再可用,这一更改提高了平台的隐私性。

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

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.

Dnsjava 库也受到影响,并且 dnsjava 中使用的检测方法不知道 Oreo 的变化。

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!

Varun Anand 解决方案适用于奥利奥,但在不处理与默认路由的连接方面存在弱点。因此,结果可能会因首先进入结果的无效 DNS 服务器而中毒,并且调用者可能会花费大量时间迭代列表并尝试连接到无法访问的 DNS 服务器。这已修复到我的解决方案中。Varun Anand 解决方案的另一个问题是这仅适用于 API 21 及更高版本。但我必须说,编写自己的解决方案对我来说是金矿。那谢谢啦!

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 服务器检测器类,您可以在任何 android 版本上使用它。包含完整的评论以回答原因和方式。

/**
 * 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

}

回答by AllThatICode

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'

以下适用于 API 21 及更高版本。它为 WiFi 和蜂窝接口返回正确的 dns 服务器。我已经验证了 shell 实用程序“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();
        }
    }

回答by Flow

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

我推荐dnsjava在 Android 上使用复杂的 DNS。让我们看看 dnsjava 如何确定连接的当前活动 DNS 服务器。从 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
    }
}

回答by Grimmace

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 reallyslow. 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.
  1. runDHCP真的很慢。它可能需要长达 5-10 秒的时间。如果使用不当,这可能会导致严重挂起。
  2. runDCHP 似乎不适用于 3G/4G。
  3. 由于 runDCHP 是一个隐藏的 API,它可能会发生变化。事实上,它确实在 ICS 中发生了变化。在 ICS 中,它需要一个新的 DhcpInfoInternal,因此您必须创建两个不同的以支持所有电话。

回答by c0ming

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

首先将外部 JARs layoutlib.jar 添加到您的构建路径中,即 $SDK_PATH/platforms/android-xxx/data/ 中的 layoutlib.jar 文件,然后

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.

您还可以使用 $getprop 命令查看 adb shell 中的所有属性。

回答by zjg

You can use java reflection. example:

您可以使用Java反射。例子:

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