首頁>技術>

出處:https://www.mobibrw.com/2020/28412

一、什麼是DNS

DNS(Domain Name System,域名系統),dns用於將域名解析解析為ip地址。

例如:給你www.baidu.com的主機名,你給

我查出對應的ip地址:163.177.151.109。一些主機名還會有別名,如www.baidu.com就

有別名www.a.shifen.com,甚至不止一個別名,或一個別名有2個ip地址。在linux機子

上,執行nslookup(name service lookup)就是進行域名解析。如下面:

~$ nslookup www.baidu.comServer:         127.0.0.1Address:        127.0.0.1#53 Non-authoritative answer:www.baidu.com   canonical name = www.a.shifen.com.Name:   www.a.shifen.comAddress: 163.177.151.109Name:   www.a.shifen.comAddress: 163.177.151.110

DNS工作方式分為遞迴查詢和迭代查詢,具體可參考下圖

二、DNS快取

所謂DNS快取有兩種,比如主從同步快取和本地快取,這裡對於手機來說,重點是本地DNS快取。Android基於Linux系統,對於Android App來說,這個快取又多了java層。

2.1 使用場景

當然,我們需要明白在Android App中那些場景需要進行,這才是最重要的,有時候其實並沒有必要去更新快取。總結一下,這裡的場景無非如下幾種:

場景一:存在多個運營商或者多個地區的分散式業務系統

比如網際網路分散式業務系統,採取的是分割槽域、分運營商的方式不是業務系統。

場景二:存在多個域名的業務系統,需要提前解析並且快取ip

場景三:ip地址唯一,但是存在多個子域名高併發請求

綜上所述:我們可以理解為,當且僅當域名和ip地址的關係是“一對多”、“多對多”和“多對一”的情況下,可適當更新DNS快取。

2.2系統版本情況說明

Android 4.3之前的TTL(Time To Live)分為正負兩種有效期,正有效期為10分鐘,最大快取為120個,採用TTL演算法回收。

// 預設有效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;

Android 4.3+的系統,快取修正為2秒,最大快取為16個,採用LRU演算法和TTL演算法進行回收。

private static final long TTL_NANOS = 2 * 1000000000L;

注意:以上程式碼參見java.net.AddressCache.java

三、Android DNS快取更新

3.1、修正快取過期時間

在Android4.3之前,TTL可以用個System.setProperties進行設定,就可以將TTL修正為何Android 4.3+一致的生存時間

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

3.2 實現DNS-Prefetch

步驟3.1只是讓快取過期時間縮短了,一定程度上處理了Android 4.3之前系統的不足。但是,對於存在域名和ip“一對多”,“多對多”和“多對一”的分散式系統,如果出現網路切換,那麼下次獲“ 可能 ”取依舊比較耗時。因此,預獲取dns是非常必要的。那麼如何實現DNS-Prefetch呢

首先,我們需要統一規範介面

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

實現介面

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 Arrays.asList(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 Lists.newArrayList();        }    }     /**     * 清除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;        }    }}

使用時機

通常網路切換後,並且下次聯網成功時,我們prefetch時最好的時間,這裡我們需要透過Broadcast+IntentService

對於廣播部分,我們需要監聽如下兩個Action(這裡推薦使用動態廣播)

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

廣播實現程式碼

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

DnsUpdateIntentService程式碼如下

public class DnsUpdateIntentService extends IntentService {     public DnsUpdateIntentService() {        super(DnsUpdateIntentService.class.getName());    }    @Override    protected void onHandleIntent(@Nullable Intent intent) {        runTask();    }    private void runTask() {       GFLog.d(DnsUpdateIntentService.class.getSimpleName()," startDns : 開始更新DNS ");        updateDnsCache();       GFLog.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;        }    } }

注意:DnsUpdateIntentService不可以註冊為多程序,否則快取無法更新

3.3、DNS防篡改與安全

Android 4.3之前的DNS可能存在被汙染的可能,如修改resolv.conf檔案,在Android 4.3+之後,統一使用Netd方式,安全性上有所提高。因此,對Android 4.3之前的系統,建議使用HttpDNS等方案,此外採取HTTPS的通訊方式,一定程度上幾乎可以絕對避免此類問題的發生。

此外,我們在 ip與域名對應數量 不大的app中,可以在App中提前建立不同機房的域名對映也是一種放置篡改的方案。

3.4、Android底層DNS更新

Android基於linux,底層透過Libcore.so更新DNS,目前沒有方式來更新Linux層面的DNS快取。那麼,我們的DNS-Prefetch功能是否有必要呢?這個問題我們需要明確,雖然我們不一定能更新底層DNS,但是,可以促進底層DNS更新,類似System.gc()的作用。

參考連結

出處:https://www.mobibrw.com/2020/28412

20
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Service workers:漸進式網頁應用 背後的小英雄