Android DNS update with Prefetch

1. What is DNS

DNS (Domain Name System, Domain Name System), dns is used to resolve domain names into IP addresses.

For example: give you the host name of www.baidu.com, you 
can find out the corresponding ip address for me: 163.177.151.109. Some host names will also have aliases, such as www.baidu.com 
has an alias www.a.shifen.com, or even more than one alias, or one alias has 2 ip addresses. On a Linux machine 
, running nslookup (name service lookup) is for domain name resolution. As follows:

~$ nslookup www.baidu.com
Server:         127.0.0.1
Address:        127.0.0.1#53

Non-authoritative answer:
www.baidu.com   canonical name = www.a.shifen.com.
Name:   www.a.shifen.com
Address: 163.177.151.109
Name:   www.a.shifen.com
Address: 163.177.151.110

The working mode of DNS is divided into recursive query and iterative query. For details, please refer to the following figure

 

DNS can also be used for load balancing, domain name pollution, firewalls, which are not discussed here.

 

2. DNS cache

There are two so-called DNS caches, such as master-slave synchronization cache and local cache. For mobile phones, the focus is on local DNS cache. Android gives Linux, and for Android App, this cache has an additional java layer.

2.1 Usage scenarios

Of course, we need to understand which scenarios need to be performed in the Android App. This is the most important thing. Sometimes it is not necessary to update the cache. To sum up, the scenarios here are nothing more than the following:

 

Scenario 1: There are distributed business systems with multiple operators or regions

For example, the Internet distributed business system adopts the method of sub-region and sub-operator, not a business system.

Scenario 2: There are business systems with multiple domain names, which need to be resolved and cached in advance

<link rel="dns-prefetch" href="//g.alicdn.com" />
<link rel="dns-prefetch" href="//img.alicdn.com" />
<link rel="dns-prefetch" href="//tui.taobao.com" />

This is the dns-prefetch link of taobao.com, this step is to speed up the dns of other pages

Scenario 3: The IP address is unique, but there are multiple subdomains with high concurrent requests

 

To sum up: we can understand that if and only if the relationship between domain name and ip address is "one-to-many", "many-to-many" and "many-to-one", the DNS cache can be updated appropriately.

 

2.2 System version description

The TTL (Time To Live) before Android 4.3 is divided into positive and negative validity periods. The positive validity period is 10 minutes, and the maximum cache is 120. The TTL algorithm is used for recycling.

// 默认有效DNS缓存时间(TTL). 600 seconds (10 minutes).
private static final long DEFAULT_POSITIVE_TTL_NANOS = 600 * 1000000000L;

// 默认无效缓存时间(TTL). 10 seconds.
private static final long DEFAULT_NEGATIVE_TTL_NANOS = 10 * 1000000000L;

For Android 4.3+ systems, the cache correction is 2 seconds, the maximum cache is 16, and the LRU algorithm and TTL algorithm are used for recycling.

private static final long TTL_NANOS = 2 * 1000000000L;

Note: See java.net.AddressCache.java for the above code

 

3. Android DNS cache update

3.1. Correct cache expiration time

Before Android 4.3, the TTL can be set with System.setProperties, and the TTL can be corrected why Android 4.3+ has a consistent time-to-live

Security.setProperty("networkaddress.cache.ttl", String.valueOf(2 * 1000000000L));  
Security.setProperty("networkaddress.cache.negative.ttl", String.valueOf(2 * 1000000000L))

 

3.2 Implement DNS-Prefetch

Step 3.1 only shortens the cache expiration time, and to a certain extent addresses the shortcomings of the system before Android 4.3. However, for a distributed system with domain name and ip "one-to-many", "many-to-many" and "many-to-one", if there is a network switch, it will still be time-consuming to obtain the " possible " next time. Therefore, prefetching dns is very necessary. So how to implement DNS-Prefetch?

First, we need to unify the specification interface

public interface Dns {

  Dns SYSTEM = new Dns() {
    @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
      if (hostname == null) throw new UnknownHostException("hostname == null");
      return Arrays.asList(InetAddress.getAllByName(hostname));
    }
  };

  List<InetAddress> lookup(String hostname) throws UnknownHostException;
}

implement interface

public class DnsManager implements Dns {

    private static DnsManager singleInstance;
    private  final  TreeSet<String>  HOST_SET = new TreeSet<String>();

    public static DnsManager getDefault(){
        if(singleInstance==null) {
            synchronized (DnsManager.class)
            {
                if (singleInstance == null) {
                    singleInstance = new DnsManager();
                }
            }
        }
        return singleInstance;
    }


    @Override
    public synchronized List<InetAddress> lookup(String hostname) throws UnknownHostException {
        try {
            if(TextUtils.isEmpty(hostname) || TextUtils.isEmpty(hostname.trim())){
                throw new UnknownHostException("hostname == null");
            }
            List<InetAddress> list = Dns.SYSTEM.lookup(hostname);
            HOST_SET.add(hostname);
            return list;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    public synchronized String quickLookup(String hostname) throws UnknownHostException {

        try {
            if(TextUtils.isEmpty(hostname) || TextUtils.isEmpty(hostname.trim())){
                throw new UnknownHostException("hostname == null");
            }
            final Uri uri = Uri.parse(hostname);
            InetAddress inetAddress = InetAddress.getByName(uri.getHost());
            if(inetAddress==null) {
                Throw.exception("unkown host",UnknownHostException.class);
            }
            String dnsIp = inetAddress.getHostAddress();
            HOST_SET.add(hostname);
            return  dnsIp;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 清除dns缓存
     */
    public synchronized void clearDnsCache(){
        try {
            ReflectUtils.invokeMethodByName(InetAddress.class, "clearDnsCache");
        }catch (Exception e){
            e.printStackTrace();
            return;
        }
    }

    /**
     * 获取主机集合
     * @return
     */
    public synchronized  TreeSet<String> getHostSet() {
        return HOST_SET;
    }

    /**
     * 预加载DNS
     * @param hosts
     */
    public synchronized void prefetchDns(List<String> hosts) {
        if(hosts==null && hosts.size()==0) return;
        for (String hostname:hosts ) {
            prefetchDns(hostname);
        }
    }

    /**
     * 预加载DNS
     * @param hostname
     */
    public synchronized void prefetchDns(String hostname) {
        try{
            InetAddress.getAllByName(hostname);
        }catch (Exception e){
            e.printStackTrace();
            return;
        }
    }
}

When to use

Usually after the network is switched, and the next time the network is successful, the best time for us to prefetch, here we need to pass Broadcast+IntentService

For the broadcast part, we need to monitor the following two Actions (dynamic broadcast is recommended here)

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);

Broadcast implementation code

public class NetStateChangeReceiver extends BroadcastReceiver{
    private static final String TAG = NetStateChangeReceiver.class.getSimpleName();

    private AtomicReference<String> pendingNetworkState = null;
    private AtomicReference<String> pendingSSID = null;

    public NetStateChangeReceiver() {
        pendingNetworkState = new AtomicReference<String>();
        pendingSSID = new AtomicReference<>();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
            NetworkType networkType = NetworkUtils.getNetworkType(context);
            notifyObservers(networkType);
        }
        if(shouldStartDnsUpdateService(context,intent)) {
            Intent cloneFilter = intent.cloneFilter();
            cloneFilter.setClass(context, DnsUpdateIntentService.class);
            context.startService(cloneFilter);
        }
    }
    //网络可用并且网络切换的情况下启动IntentService更新
    public boolean shouldStartDnsUpdateService(Context context,Intent intent){

        if(NetworkUtils.isAvailable(context)){
            NetworkType type = NetworkUtils.getNetworkType(context);
            if(type==null) return false ;
            String newState = type.toString();
            String lastState = pendingNetworkState.get();
            if(!TextUtils.isEmpty(lastState) && !lastState.equals(newState))
            {
                pendingNetworkState.set(newState);
                return true;
            }else{
                pendingNetworkState.set(newState);
                if(NetworkUtils.isWifiConnected(context)){
                    WifiInfo wifiInfo= intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
                    if(wifiInfo!=null)
                    {
                        String nextSSID = wifiInfo.getSSID();
                        String lastSSID = pendingSSID.get();

                        if(nextSSID!=null && nextSSID.equals(lastSSID))
                        {
                            return true;
                        }
                    }
                }

            }
        }else{
            pendingNetworkState.set(NetworkType.NETWORK_NO.toString());
        }
        return false;
    }
}

The DnsUpdateIntentService code is as follows

public class DnsUpdateIntentService extends IntentService {

    public DnsUpdateIntentService() {
        super(DnsUpdateIntentService.class.getName());
    }
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        runTask();
    }
    private void runTask() {
        NCFLog.d(DnsUpdateIntentService.class.getSimpleName()," startDns : 开始更新DNS ");
        updateDnsCache();
        NCFLog.d(DnsUpdateIntentService.class.getSimpleName()," endDns : DNS更新完成 ");
    }

    private void updateDnsCache() {
        try{
            DnsManager dm = DnsManager.getDefault();
            dm.clearDnsCache();
            TreeSet<String> hostSet = dm.getHostSet();
            List<String> hosts = new ArrayList<>();
            hosts.addAll(hostSet);
            dm.prefetchDns(hosts);
        }catch (Exception e){
            e.printStackTrace();
            return;
        }
    }

}

Note: DnsUpdateIntentService cannot be registered as a multi-process, otherwise the cache cannot be updated

3.3. DNS anti-tampering and security

The DNS before Android 4.3 may be polluted, such as modifying the resolv.conf file. After Android 4.3+, the Netd method is used uniformly, which improves the security. Therefore, for the system before Android 4.3, it is recommended to use schemes such as HttpDNS, in addition to adopting the communication method of HTTPS, to a certain extent, the occurrence of such problems can be almost absolutely avoided.

 

3.4, Android underlying DNS update

Android is based on linux, and the bottom layer updates DNS through Libcore.so. Currently, there is no way to update the DNS cache at the Linux level.

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326086067&siteId=291194637