2010-06-18 16 views
22

Estoy tratando de obtener las direcciones de los servidores DNS actualmente utilizados en mi aplicación, ya sea que esté conectado a través de Wifi o un dispositivo móvil. El objeto DhcpInfo debería proporcionar esto, pero ¿cómo puedo obtener un objeto válido DhcpInfo?¿Cómo se obtienen los servidores DNS actuales para Android?

Respuesta

9

android.net.ConnectivityManager le entregará una matriz de NetworkInfo utilizando getAllNetworkInfo(). Luego use android.net.NetworkUtils.runDhcp() para obtener un DhcpInfo para cualquier interfaz de red dada - la estructura DhcpInfo tiene la dirección IP para dns1 y dns2 para esa interfaz (que son valores enteros que representan la dirección IP).

En caso de que se preguntan cómo diablos se van a transformar el número entero en una dirección IP, se puede hacer esto:

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

Editar

También puede obtener un objeto DchcpInfo haciendo algo como esto:

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

Pero no es android.net.NetworkUtils.runDhcp() una función nativa? ¿Cómo accedo? – John

+6

Ok, obtener el DhcpInfo de Wifi es sencillo, pero ¿cómo lo haces para el móvil? (UMTS, etc.) ¿Tengo que cargar alguna biblioteca nativa para obtener acceso a android.net.NetworkUtils.runDhcp()? – John

+0

@John: publiqué una solución nativa que funciona para redes móviles. Echar un vistazo. – Grimmace

2

recomiendo dnsjava para su uso en DNS complejo Android. Veamos cómo dnsjava determina el servidor DNS activo actual para la conexión. 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 
    } 
} 
+0

Advertencia: este código puede causar bloqueos en Runtime.getRuntime(). Exec() – Nappy

27

Llamada para el getRuntime().exec puede hang su aplicación.

android.net.NetworkUtils.runDhcp() causan solicitudes de red innecesarias.

así que prefiero hacer esto:

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

Si está trabajando en AOSP (aplicación del sistema o biblioteca), puede importar directamente 'android.os.SystemProperties' y usar' public static String get (String key, String def) ' – Cyctemic

+1

Parece que esto ya no está permitido en Android Oreo. ¿Alguna actualización aquí? – atsakiridis

+1

Sí, esto ya no está permitido en oreo – Hades

2

Una alternativa nativa es:

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

O mejor aún para obtener una lista completa:

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

Hay algunas ventajas para hacerlo de esta manera:

  1. runDHCP es realmente lento. Puede tomar hasta 5-10 segundos. Esto puede causar una caída importante cuando se usa incorrectamente.
  2. runDCHP no parece funcionar para 3G/4G.
  3. Dado que runDCHP es una API oculta, está sujeta a cambios. De hecho, sí cambió en ICS. En ICS se necesita un nuevo DhcpInfoInternal, por lo que tendrá que crear dos diferentes para admitir todos los teléfonos.
+4

__system_property_get() la función se elimina en Android L –

2

primero Añadir JAR externos layoutlib.jar a su trayectoria de la estructura, el archivo layoutlib.jar en SDK_PATH $/plataformas/android-xxx/data /, entonces

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

también se puede ver todos los bienes en adb shell con el comando $ getprop.

+0

funciona con 'getprop net.dns1' en termux. – Ben

-1

Puede usar la reflexión de java.ejemplo:

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

Los siguientes trabajos para API 21 y superior. Devuelve servidores dns correctos para las interfaces WiFi y Celular. He verificado los valores devueltos con utilidad de 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(); 
     } 
    } 
+0

Esta fue una mina de oro por proporcionar mi propia solución que maneja todas las versiones. Por favor revisa mi respuesta. ¡Gracias de nuevo! –

0

Desafortunadamente la mayoría de las soluciones presentadas ya no funcionan en Android 8.0

Android Oficial del Estado la documentación esto muy claramente en el artículo de Android 8.0 Cambios de comportamiento. Las propiedades del sistema net.dns1, net.dns2, net.dns3 y net.dns4 ya no están disponibles, un cambio que mejora la privacidad en la plataforma.

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

biblioteca Dnsjava también se lado afecto y los métodos de detección utilizados en dnsjava no son conscientes de los cambios de Oreo.

La solución de Varun Anand funciona en Oreo pero tiene una debilidad al no manejar la conexión con las rutas predeterminadas. Debido a esto, el resultado puede ser envenenado con servidores DNS no válidos que entran primero en el resultado y la persona que llama puede pasar mucho tiempo iterando la lista y tratando de conectarse a servidores DNS ilegibles. Esto fue arreglado en mi solución. Otro problema con la solución Varun Anand es que esto solo funciona para API 21 y superior. Pero debo decir que fue oro mío para mí escribir mi propia solución. ¡Así que gracias!

Para su comodidad proporcioné una clase de detector de servidores DNS completa que puede usar que funciona en cualquier versión de Android. Se incluyen comentarios completos para responder a por qué y cómo.

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

} 
Cuestiones relacionadas