Android HttpDns:我的域名我做主

Android HttpDns:我的域名我做主

域名劫持

之前公司的產品遇到了用戶忽然無法登陸使用的問題,後來查明是因爲在用戶發佈的內容裏有一張被判定爲黃圖的圖片,導致我們的域名被運營商封鎖。還有一種情況,部分地區的用戶使用我們的產品時頁面上被莫名插入了廣告,後來發現也是運營商搞的鬼,劫持了我們的網站數據,並私自插入了廣告。這裏暴露出了依賴網絡運營商進行域名解析的一些問題,總結如下:

1. 域名封鎖。運營商可以因爲各種理由封鎖你的域名,導致所有請求你的網站的請求都因爲無法解析域名而失敗。
2. 域名劫持。運營商可以根據你訪問的域名進行有針對性的廣告注入,有損產品的用戶體驗。

爲了避免自己的命運被其他人操縱的局面,我們採用了HttpDns技術來繞開運營商的控制,解決這類問題。

HttpDns是一種通過Http服務器提供域名解析功能的技術,通過向Http服務器的80端口進行請求,代替傳統的DNS服務器的53端口進行請求。利用這種技術,我們直接向我們信賴的Http服務器(我們自己搭建的,或是第三方提供的)詢問我們要訪問的域名對應的IP,而不再依賴網絡運營商進行域名解析,從而解決了上面兩個問題。接下來介紹一下HttpDns技術的使用。

HttpDns第三方服務主要有兩個提供商:
DNSPod D+
阿里雲 HttpDns

以DNSPod D+爲例介紹HttpDns的使用。數據請求和應答均使用 http 協議。
請求格式爲:

http://119.29.29.29/d?dn=www.chunyuyisheng.com

dn表示要查詢的域名。該請求返回以分號分隔的ip地址,如下:

106.75.8.170;106.75.28.177

如果查詢失敗返回空結果。

我們發送網絡請求使用的是OkHttp庫,可以通過OkHttpClient的setDns方法實現默認Dns的替換,代碼如下:
首先繼承默認的Dns,重寫lookup方法:

public class HttpDns implements Dns {    
    // 需要進行httpdns處理轉換的host列表,這裏我們只對部分跟我們公司服務相關的域名
    // 利用HttpDns進行解析,其他的還是用默認的域名解析方式    
    private CopyOnWriteArrayList<String> mHostList;    
    public HttpDns(List<String> hostList) {        
        mHostList = new CopyOnWriteArrayList<>();        
        mHostList.addAll(hostList);    
    }    

    @Override    
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {        
        if (hostname == null) {            
            throw new UnknownHostException("host == null");        
        } else {            
            if (mHostList != null && mHostList.contains(hostname)) {                
                try {                    
                    String ipAddr = HttpDnsHelper.getInstance().getIpStrForHost(hostname);                    
                    if (!TextUtils.isEmpty(ipAddr)) {                        
                        List<InetAddress> addresses = new ArrayList<>();                        
                        addresses.add(InetAddress.getByName(ipAddr));                        
                        Log.d("httpdns", "HttpDns hint, host:" + hostname + "  ip:" + ipAddr);                        
                        return addresses;                    
                    }                
                } catch (Exception ignored) {}            
              }            

            //未能從httpdns獲取ip地址,使用系統dns獲取            
            return SYSTEM.lookup(hostname);        
        }    
    }
}

在lookup代碼中,我們先判斷要查找的hostname是不是在指定列表中,如果在裏面,我們就通過HttpDns請求其ip地址,否則用默認的dns進行解析。HttpDns請求的過程通過函數getIpStrForHost實現,該函數如下:

private Map<String, DnsInfo> mDnsMap;
public String getIpStrForHost(String host) {    
    if (!TextUtils.isEmpty(host)) {        
        DnsInfo dnsInfo = mDnsMap.get(host);        
        if (dnsInfo != null && dnsInfo.isIpValid() && !dnsInfo.isExpired()) {            
            //如果可以獲得合法的dns info,返回ip str            
            return dnsInfo.getIpStr();        
        } else {            
            //無法獲得合法的dns info, 異步更新host對應的cache            
            updateCacheForHost(host);        
        }
    }    
return null;
}

這裏對HttpDns的結果使用了緩存機制,首先判斷是否已經緩存了該host的ip地址,如果是,並且沒過期,直接從緩存獲取;否則重新獲取並更新緩存。進行HttpDns請求並更新緩存的代碼在updateCacheForHost中:

public void updateCacheForHost(final String host) {
    String url = String.format("http://119.29.29.29/d?dn=%s&ttl=1", host);      
    Request request = new Request.Builder().url(url).build();    
    mOkHttpClient.newCall(request).enqueue(new Callback() {        
        @Override        
        public void onFailure(Request request, IOException e) {            
            Log.d("httpdns", "get ip for host(" + host + ") failed");        
        }        
        @Override        
        public void onResponse(Response response) throws IOException {            
            InputStream inputStream = response.body().byteStream();            
            String data = readInputStream(inputStream);            
            Log.d("httpdns", "get ip for host(" + host + ") success :" + data);            
            DnsInfo dnsInfo = parseResponseString(host, data);            
            if (dnsInfo != null) {                
                mDnsMap.put(host, dnsInfo);                
            }
        }
    });
}

通過一個異步請求,發送Http請求到HttpDns服務器,返回ip地址,得到域名對應的ip地址,完成Dns過程,並更新本地緩存。

最後將這個改進後的HttpDns替換掉OkHttpClient默認的dns,完成OkHttp新dns的設置:

okHttpClient.setDns(new HttpDns(getHttpDnsHost(context)));

參考:
http://blog.csdn.net/sbsujjbcy/article/details/50532797
http://mp.weixin.qq.com/s?__biz=MzA3ODgyNzcwMw==&mid=201837080&idx=1&sn=b2a152b84df1c7dbd294ea66037cf262&scene=2&from=timeline&isappinstalled=0&utm_source=tuicool
http://blog.csdn.net/charleslei/article/details/41154139

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章