圖片來源:https://unsplash.com/photos/Q...
作者:念山
背景
隨著技術的發展,網路環境也變得越來越複雜,而對於一個以網路資料傳輸提供服務的 App 來講,在複雜多變的網路環境下安全穩定有效的提供好服務顯得尤為重要。而為了提供安全穩定有效的 HTTP 網路服務,我們從網路請求的初始階段 DNS 解析上保證 DNS 安全性的技術:去分析下蘋果2022年 WWDC 講到的 DNSSEC 技術和我們雲音樂現行的 HTTPDNS 並做下對比。
什麼是 DNS
域名系統(Domain Name System,DNS) 則是將域名解析 IP 地址的一項網際網路基礎服務,提供該服務的伺服器稱為 域名伺服器(Domain Name Server)。
域名(Domain Name,Domain) 是一個在網際網路上標識主機或主機組的名稱,相當於 IP 地址的別名,相對於晦澀難記的 IP 地址,域名更顯得易於記憶。
域名系統是怎麼工作的呢
網際網路上的域名系統是一個分散式的系統,結構上是一個四層的樹狀層次結構:
- 本地域名伺服器(Local Name Server,local DNS):如果透過 DHCP 配置,local DNS 由網際網路服務提供商(ISP,如聯通、電信)提供
- 根域名伺服器(Root Name Server):當 local DNS 查詢不到解析結果時,第一步會向它進行查詢,並獲取頂級域名伺服器的IP地址。全球一共有 13 個根域名伺服器(除了它們的映象),它們並不直接用於域名解析,僅用於指出可查詢的頂級域名伺服器。
- 頂級域名伺服器(Top-level Name Server):負責管理在該頂級域名伺服器下注冊的二級域名,例如.com 頂級域名伺服器,而 baidu.com 權威伺服器是註冊在 .com 的權威域名伺服器
- 權威域名伺服器(Authoritative Name Server):在特定區域內具有唯一性,負責維護該區域內的域名與 IP 地址的對映關係。在 DNS 應答報文中,標誌位 AA 標識本次 DNS 記錄是否來自權威域名伺服器,否則可能來自快取
模擬流程
那以我們主站域名 interface.music.163.com 為例,來看一下 DNS 的過程:
- 0、首先檢查 DNS 快取,如果快取老化或未命中,客戶端需要向 local DNS 傳送查詢請求報文
- 1、客戶端向 local DNS 傳送查詢報文 query interface.music.163.com,local DNS 首先檢查自身快取,如果存在記錄則直接返回結果,查詢結束;如果快取老化或未命中,則:
- 2、local DNS 向根域名伺服器傳送查詢報文 query interface.music.163.com,返回 .com 頂級域名伺服器的地址(如果查無記錄)
- 3、local DNS 向 .com 頂級域名伺服器傳送查詢報文 query interface.music.163.com,返回interface.music.163.com所在的權威域名伺服器的地址(如果查無記錄)
- 4、local DNS 向 interface.music.163.com 權威域名伺服器傳送查詢報文 query interface.music.163.com,得到 ip 地址,存入自身快取並返回給客戶端
DNS 特點
DNS 從 1983 年設計推出到現在已經在全球運作 40 年了,裝置支援高,但是由於最初設計的時候沒有考慮安全相關的問題,報文無鑑權和加密相關的資訊,帶來了不穩定的因素。
優勢
- 系統支援
- 速度快
劣勢
中間人攻擊
- DNS 欺騙
- DNS 劫持
- 結構複雜,中間變數多
針對 DNS 不安全的情況,業界也在透過 DNSSEC、HTTPDNS 等技術方案補充來去保證安全提高服務可用。
什麼是 DNSSEC
DNSSEC 可以說是在原有 DNS 基礎之上做的擴充套件。
網域名稱系統安全性擴充 (DNSSEC) 可為網域名稱的 DNS (網域名稱系統) 加上電子簽名,藉此判斷來源網路名稱的真實性。此功能可以保護網路使用者不受假造 DNS 資料的威脅,讓使用者要求正確網址時不會取得其他有意誤導或惡意製作的網址。
DNSSEC 工作的流程
DNSSEC 透過建立信任鏈來驗證記錄
舉個例子:裝置想要解析 www.example.org 並啟用 DNSSEC 驗證,過程如下:
- 傳送詢問 IP 地址、簽名和金鑰的查詢,透過響應可以建立從 IP 地址到金鑰 1 的信任關係
- 客戶端向父區域 org 傳送查詢,詢問可用於驗證金鑰 1 的記錄,建立從金鑰 1 到金鑰 2 的信任關係
- 裝置遞迴地重複這個過程,直到它到達根域
DNSSEC 特點
透過流程總結下來
優勢
提供校驗保證安全
劣勢
需要路由器支援,路由器必須支援處理大於正常大小的 DNS 包
- 正常的 DNS 包大小為 512 位元組, DNSSEC 則大於 512 位元組,國內較多路由器會丟棄超過 512 位元組的 DNS 包
- 域名註冊服務商配置大部分是付費的
雲商支援弱
- 較多雲商不支援 DNSSEC 解析
- 支援 DNSSEC 解析的基本還收費
- 需要權威伺服器支援
系統相容差
- iOS16+ 支援
- macOS Ventura 以上才支援
是否要選擇 DNSSEC?
DNSSEC 方案對於我們 App 來講不可控因素太多,且可擴充套件性較弱所以我們選擇了一套同樣安全,且更為可控的方案-- HTTPDNS。
HTTPDNS
相較於 DN S和 DNSSEC,對於客戶端來講有一種可控性更強和更安全的方案:HTTPDNS,而 HTTPDNS 也是我們雲音樂現在正在執行的方案。
什麼是 HTTPDNS
DNSOverHTTP,透過 HTTP 報文請求來去拉取 DNS 的結果,客戶端拿到結果後拿 IP 替換請求的 host 並設定 SNI 來去進行IP直連的請求,達到控制請求目的 IP 選擇的結果,達到等同 DNS 的功能。
HTTPDNS 流程
- 客戶端請求自己 HTTPDNS 伺服器拿到 IP
- 發請求替換 host 為對應的 IP
- 設定 SNI 來新增原始 host 資訊
HTTPDNS 特點
優勢
- 可定製化強 : 服務走自己伺服器
- 安全 : 走 https 請求
劣勢
- 相較於 LocalDNS 時間損耗長:由於採用 HTTP 報文來去傳輸 domain 與 IP 的對映表
- 需要自己處理替換域名的操作。
- 具有一定的安全風險:iPhone 需要呼叫私有 API 去設定 SNI
安全風險
一個比較大的風險點是在使用私有 API 來去設定 SNI。
在基於目前網路基本使用 NSURLSession 的大背景下,在使用 HTTPDNS 技術的情況下有個重要的一個過程是透過私有 API 來設定 SNI 資訊透過 HTTPS 的握手校驗。但是私有 API 有隨時被封禁的風險,如若被封禁則使用私有 API 來做 SNI 設定的HTTPS請求都不可用。
怎麼來規避使用私有 API 這個安全風險呢?
核心問題
NSURLSession 無法控制 Socket 連線,所以只能替換 host 的方式來去處理,如果透過控制 Socket 連線池,就能規避掉 SNI 的問題
新的網路庫
我們選擇一套新的網路庫 Cronet 既能控制 Socket 連線池,又能帶來網路效能相關的提升及網路高階特性的支援。
IP 跑馬
HTTPDNS 拿到的是一個 IP 陣列,實際發請求的時候只需要一個 IP,那到底使用哪個 IP 來進行建連請求呢?那就需要透過一定的計分標準來給 IP 打分,透過 IP 跑馬來拿到最佳 IP。
計分規則
單次請求的評分
單次請求評分按照正確請求和錯誤請求兩種不同的計分方式來進行處理。
錯誤評分
把網路錯誤細分為5個等級,不同等級扣分分值不一樣
typedef NS_ENUM(NSInteger, NENetErrorLevel)
{
NENetErrorLevelNone, // 無錯誤,不扣分
NENetErrorLevelDefault, // 預設,扣1分
NENetErrorLevelisCancel, //取消不算錯誤,不扣分
NENetErrorLevelNormal, // 普通級別 扣10分
NENetErrorLevelSerious, // 嚴重 扣20分
};
// 把對應的NSURLSession的網路報錯對應到錯誤等級進行評分
- (NENetErrorLevel)errorLevelForError:(NSError *)error
{
NENetErrorLevel errorLevel =NENetErrorLevelNone;
BOOL isCancel = NO;
BOOL isChainError = NO;
BOOL isTimeout = NO;
NSError *urlError = error;
NSInteger errorCode = urlError.code;
if ([urlError.domain isEqualToString:NSURLErrorDomain] ||
[urlError.domain isEqualToString:@"AFNetworkingErrorDomain"])
{
if (errorCode == NSURLErrorCannotConnectToHost)
{
isChainError = YES;
}
isTimeout = errorCode == NSURLErrorTimedOut;
isCancel = errorCode == NSURLErrorCancelled;
}
else if ([urlError.domain isEqualToString:NSPOSIXErrorDomain])
{
if (errorCode == 54 || //ECONNRESET
errorCode == 57 || //ENOTCONN
errorCode == 60 || //ETIMEDOUT
errorCode == 61 ||
errorCode == 65
) //ECONNREFUSED
{
isChainError = YES;
}
}
if (isCancel) {
errorLevel = NENetErrorLevelisCancel;
} else if (isChainError) {
errorLevel = NENetErrorLevelSerious;
} else if (isTimeout) {
errorLevel = NENetErrorLevelNormal;
} else if (error) {
errorLevel = NENetErrorLevelDefault;
}
return errorLevel;
}
正確的評分
正確的評分採用請求速度來去考量分值,選擇基準線速度和時間,在基準線上線浮動進行算分。
耗時<=2.5s 才會去按照基準線進行計算分
耗時>4.5s 開始扣分
// 計算公式
- (double)calcllateSuccessMarkWithDataLength:(NSUInteger)dataLength cost:(NSTimeInterval)cost
{
if (cost == 0) {
return 0;
}
double mark;
long delta = 0;
if (cost <= CALL_TIME_GOOD_BENCH_MARK) { //如果<= 2.25s, 才有積分機會
delta = CALL_TIME_GOOD_BENCH_MARK - cost;
} else if (cost > CALL_TIME_BAD_BENCH_MARK) { //如果 > 4.5s, 則開始扣分
delta = CALL_TIME_BAD_BENCH_MARK - cost;
}
// 超時不一定是IP有問題
//透過耗時維度計算出的mark 最低-5分
mark = MAX(delta,-5);
if (dataLength > 0 && mark > 0) {
double speed = dataLength / (cost + 0.0f);
if (speed < CALL_SPEED_GOOD_BENCH_MARK) { //利用speed, 將請求體小的請求分數降低
mark = speed / CALL_SPEED_GOOD_BENCH_MARK * mark;
}
}
return mark;
}
IP 總分
IP 總分有兩次策略:累計計分、瞬時計分
累計計分
當前網路環境下(WiFi/蜂窩)的所有請求的分值的加和。
瞬時計分
當前網路環境下(WiFi/蜂窩)的當前請求的分值就是IP的最終得分。
選擇IP
請求發出的時候根據拿到的 IP 組按照 IP 的跑分拿最高分作為當成請求的 IP。
整體流程圖
4. 小結與展望
網路安全不容小覷,各大廠商都在盡力去透過技術手段來去維護網路安全,來給使用者提供一個安全可靠的網路環境。我們也希望蘋果在後續可以在DNS相關方面提供更開放可由開發人員來去實現策略的 API,在保證網路安全基礎上又能讓開發人員能有一定的靈活性。針對文中 Cronet 的使用由於還在灰度推進中,等推全後也將梳理篇 Cronet 接入帶來的利好及坑點來和大家交流。
參考資料
[1] WWDC - 10079
[2] DNSSEC 協議
[3] DNS劫持
[4] DNS欺騙
[6] Cronet 網路請求流程
本文釋出自網易雲音樂技術團隊,文章未經授權禁止任何形式的轉載。我們常年招收各類技術崗位,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com !