HttpDNS功能說明及實現
功能說明
HTTPDNS使用HTTP協議進行域名解析,代替現有基於UDP的DNS協議,域名解析請求直接傳送到阿里雲的HTTPDNS伺服器,從而繞過運營商的Local DNS,能夠避免Local DNS造成的域名劫持問題和排程不精準問題。
功能 | 說明 |
---|---|
防劫持 | 繞過運營商Local DNS,避免域名劫持,讓每一次訪問都暢通無阻。 |
精準排程 | 基於訪問的來源IP,獲得最精準的解析結果,讓客戶端就近接入業務節點。 |
0ms解析延遲 | 通過熱點域名預解析、快取DNS解析結果、解析結果懶更新策略等方式實現0解析延遲 |
快速生效 | 避免Local DNS不遵循權威TTL,解析結果長時間無法更新的問題 |
降低解析失敗率 | 有效降低無線場景下解析失敗的比率 |
防劫持
HTTPDNS代替了傳統的LocalDNS完成遞迴解析的功能,基於HTTP協議的設計可以適用於幾乎所有的網路環境,同時保留了鑑權、HTTPS等更高安全性的擴充套件能力,避免惡意攻擊劫持行為。
精準排程
傳統域名解析的排程精準性問題,本質根源在於Local DNS的部署和分配機制上。由於碎片化的管理方式,這些環節的服務質量同樣很難得到保障。HTTPDNS在遞迴解析實現上優化了與權威DNS的互動,通過<a name="https://datatracker.ietf.org/doc/rfc7871">edns-client-subnet</a>協議將終端使用者的IP資訊直接交付給權威DNS,這樣權威DNS就可以忽略Local DNS IP資訊,根據終端使用者的IP資訊進行精準排程,避免Local DNS的座標干擾
DNS解析0延遲:
-
構建客戶端DNS快取;
通過合理的DNS快取,我們確保每次網路互動的DNS解析都是從記憶體中獲取IP資訊,從而大幅降低DNS解析開銷。根據業務的不同,我們可以 制訂更豐富的快取策略,如根據運營商快取,可以在網路切換的場景下複用已快取的不同運營商線路的域名IP資訊,避免網路切換後進行鏈 路重選擇引入的DNS網路解析開銷。另外,我們還可以引入IP本地化離線儲存,在客戶端重啟時快速從本地讀取域名IP資訊,大幅提升首頁 載入效率。
-
熱點域名預解析;
在客戶端啟動過程中,我們可以通過熱點域名的預解析完成熱點域名的快取載入。當真正的業務請求發生時,直接由記憶體中讀取目標域名的IP 資訊,避免傳統DNS的網路開銷。
-
懶更新策略;
絕大多數場景下業務域名的IP資訊變更並不頻繁,特別是在單次APP的使用週期內,域名解析獲取的IP往往是相同的(特殊業務場景除外)。 因此我們可以利用DNS懶更新策略來實現TTL過期後的DNS快速解析。所謂DNS懶更新策略即客戶端不主動探測域名對應IP的TTL時間,當業務 請求需要訪問某個業務域名時,查詢記憶體快取並返回該業務域名對應的IP解析結果。如果IP解析結果的TTL已過期,則在後臺進行非同步DNS網 絡解析與快取結果更新。通過上述策略,使用者的所有DNS解析都在與記憶體互動,避免了網路互動引入的延遲。
實現方案
服務端:
服務端提供API介面,app端直接通過ip地址訪問,ip地址可以有多個
請求方式:HTTP GET
URL引數說明:
名稱 | 是否必須 | 描述 |
---|---|---|
host | 必須 | 要解析的域名 |
ip | 可選 | 使用者的來源IP,如果沒指定這個引數,預設使用請求連線的源IP |
請求示例:
-
示例1(預設來源IP):http://x.x.x.x/d?host=www.suning.com
-
示例2(指定來源IP):http://x.x.x.x/d?host=www.suning.com&ip=185.53.179.6
考慮到服務IP防攻擊之類的安全風險,為保障服務可用性,HTTPDNS同時提供多個服務IP,當某個服務IP在異常情況下不可用時,可以使用其它服務IP進行重試。
請求成功時,HTTP響應的狀態碼為200,響應結果用JSON格式表示,示例如下:
{
"host": "www.suning.com",
"ips": [
"112.84.104.48"
],
"ttl": 57,
"origin_ttl": 120
}
請求失敗的響應示例:
{
"code": "MissingArgument"
}
錯誤碼列表如下:
錯誤碼 | HTTP狀態碼 | 描述 |
---|---|---|
MissingArgument | 400 | 缺少必要引數 |
InvalidHost | 400 | 域名格式不合法 |
MethodNotAllowed | 405 | 不支援的HTTP方法 |
InternalError | 500 | 服務端內部錯誤 |
錯誤處理
異常下的出錯相容邏輯,主要包括非同步請求,重試,降級
非同步請求
訪問HTTPDNS服務時,應該使用非同步請求的策略,避免解析延遲太大而對業務造成影響,特別是在網路環境異常或HTTPDNS服務IP異常不可
用時,如果用同步訪問,需要等待網路超時後才會返回解析失敗,這個超時時間較大,可能對業務的使用體驗造成很大影響。
非同步請求策略:解析域名時,如果當前快取中有TTL未過期的IP,可直接使用;如果沒有,則立刻讓此次請求降級走原生LocalDNS解析,同
時另起執行緒非同步地發起HTTPDNS請求進行解析,更新快取,這樣後續解析域名時就能命中快取。
重試
訪問HTTPDNS服務解析域名時,如果請求HTTPDNS服務端失敗,即HTTP請求沒有返回,可以進行重試。
大部分情況下,這種訪問失敗是由於網路原因引起的,重試可以解決。
降級
不管是因為什麼原因,當通過HTTPDNS服務無法獲得域名對應的IP時,都必須降級:使用標準的DNS解析,通過Local DNS去解析域名。
Android端:
OkHttp預設使用系統DNS服務InetAddress進行域名解析,但同時也暴露了自定義DNS服務的介面,通過該介面我們可以優雅地使用HttpDns。
- 自定義DNS介面
OkHttp暴露了一個Dns介面,通過實現該介面,我們可以自定義Dns服務:
public class OkHttpDns implements Dns {
private static final Dns SYSTEM = Dns.SYSTEM;
HttpDnsService httpdns;//httpdns 解析服務
private static OkHttpDns instance = null;
private OkHttpDns(Context context) {
this.httpdns = HttpDns.getService(context, "account id");
}
public static OkHttpDns getInstance(Context context) {
if(instance == null) {
instance = new OkHttpDns(context);
}
return instance;
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
//通過非同步解析介面獲取ip
String ip = httpdns.getIpByHostAsync(hostname);
if(ip != null) {
//如果ip不為null,直接使用該ip進行網路請求
List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
Log.e("OkHttpDns", "inetAddresses:" + inetAddresses);
return inetAddresses;
}
//如果返回null,走系統DNS服務解析域名
return Dns.SYSTEM.lookup(hostname);
}
}
- 建立OkHttpClient
建立OkHttpClient物件,傳入OkHttpDns物件代替預設Dns服務:
private void okhttpDnsRequest() {
OkHttpClient client = new OkHttpClient.Builder()
.dns(OkHttpDns.getInstance(getApplicationContext()))
.build();
Request request = new Request.Builder()
.url("http://www.aliyun.com")
.build();
Response response = null;
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
DataInputStream dis = new DataInputStream(response.body().byteStream());
int len;
byte[] buff = new byte[4096];
StringBuilder result = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
result.append(new String(buff, 0, len));
}
Log.d("OkHttpDns", "Response: " + result.toString());
}
});
}
- 總結
相比於通用方案,OkHttp+HttpDns有以下兩個主要優勢:
實現簡單,只需通過實現Dns介面即可接入HttpDns服務
通用性強,該方案在HTTPS,SNI以及設定Cookie等場景均適用。規避了證書校驗,域名檢查等環節
IOS端:
基於NSURLProtocol可攔截iOS系統上基於上層網路庫NSURLConnection/NSURLSession發出的網路請求;
通過以下介面註冊自定義NSURLProtocol,用於攔截上層網路請求,並建立新的網路請求接管資料傳送、接收、重定向等處理邏輯,將結果反饋給原始請求。
[NSURLProtocol registerClass:[CustomProtocol class]];
自定義NSURLProtocol處理過程概述:
- 在canInitWithRequest中過濾要需要做HTTPDNS域名解析的請求;
- 請求攔截後,做HTTPDNS域名解析;
- 解析完成後,同普通請求一樣,替換URL.host欄位,替換HTTP Header Host域,並接管該請求的資料傳送、接收、重定向等處理;
作者:剛哥說
連結:https://www.jianshu.com/p/15ff2aeb5b5b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。