How do you get the current DNS servers for Android?
I'm trying to get hold of the addresses to the currently开发者_C百科 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?
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);
}
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
}
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();
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();
}
}
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
}
}
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:
- runDHCP is really slow. It can take as long as 5-10 seconds. This can cause a major hang when used incorrectly.
- runDCHP doesn't seem to work for 3G/4G.
- 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.
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.
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();
}
精彩评论