【问题标题】:How do you get the current DNS servers for Android?您如何获取当前适用于 Android 的 DNS 服务器?
【发布时间】:2011-03-05 10:49:02
【问题描述】:

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

【问题讨论】:

    标签: android


    【解决方案1】:

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

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

    所以我更喜欢这样做:

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

    【讨论】:

    • 如果您在AOSP(系统应用或库)中工作,您可以直接导入android.os.SystemProperties并使用public static String get(String key, String def)
    • 似乎在 Android Oreo 中不再允许这样做。这里有更新吗?
    • 是的,奥利奥不再允许这样做
    • 我可以确认此方法在 Android 8.0 中不再有效 Android 官方文档在文章 Android 8.0 行为更改中非常清楚地说明了这一点。系统属性 net.dns1、net.dns2、net.dns3 和 net.dns4 不再可用,这一更改提高了平台的隐私性。 developer.android.com/about/versions/oreo/…
    【解决方案2】:

    不幸的是,大多数提出的解决方案在 Android 8.0 中都不再适用

    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

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

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

    为了您的方便,我提供了一个完整的 DNS 服务器检测器类,您可以在任何 Android 版本上使用它。 包含完整的 cmets 以回答原因和方式。

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

    【讨论】:

    • 这救了我。对于以后的任何人,使用 dnsjava,在 ResolverConfig.findAndroid 中调用此例程,将结果添加到 lserver 列表
    • 这将始终返回所有活动连接的所有 DNS 服务器。如果您在 wifi 网络上并且蜂窝连接仍处于活动状态,它将返回两个连接的 DNS 服务器,但只有 wifi 连接的服务器可以工作。
    • 类如何使用?
    • connectivityManager.getNetworkInfo 已弃用。我尝试使用 NetworkCallbacks 找到解决方案来获取 dns。
    • @Manikandan 如果您这样做,请提供解决方案,以便我可以编辑我的答案,包括您的研究。我还将研究您在这里写的内容,并在必要时尝试更新我的代码...
    【解决方案3】:

    android.net.ConnectivityManager 将使用getAllNetworkInfo() 为您提供一系列 NetworkInfo。然后使用android.net.NetworkUtils.runDhcp() 为任何给定的网络接口获取DhcpInfo - DhcpInfo 结构具有该接口的dns1dns2 的IP 地址(它们是表示IP 地址的整数值)。

    如果您想知道如何将整数转换为 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);
    }
    

    编辑

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

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

    【讨论】:

    • 但是 android.net.NetworkUtils.runDhcp() 不是原生函数吗?我如何访问它?
    • 好的,从 Wifi 获取 DhcpInfo 很简单,但是您如何为移动设备做到这一点? (UMTS 等)我是否必须加载一些本机库才能访问 android.net.NetworkUtils.runDhcp() ?
    • @John - 我发布了一个适用于移动网络的本地解决方案。看看吧。
    【解决方案4】:

    以下适用于 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();
            }
        }
    

    【讨论】:

    • 这是一个金矿,可以提供我自己的解决方案来处理所有版本。请检查我的答案。再次感谢!
    【解决方案5】:

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

    【讨论】:

    • 警告:此代码可能会导致 Runtime.getRuntime().exec() 挂起
    【解决方案6】:

    本地替代方案是:

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

    或者更全面的列表:

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

    这样做有几个好处:

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

    【讨论】:

    • __system_property_get() 函数在 Android L 中被移除
    【解决方案7】:

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

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

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

    【讨论】:

      【解决方案8】:

      你可以使用java反射。示例:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 2010-12-24
        • 2012-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多