1. 程式人生 > >詳解DNS(理論篇)

詳解DNS(理論篇)

對於 DNS(Domain Name System) 大家肯定不陌生,不就是用來將一個網站的域名轉換為對應的IP嗎。當我們發現可以上QQ但不能瀏覽網頁時,我們會想到可能是域名伺服器掛掉了;當我們用別人提供的hosts檔案瀏覽到一個“不存在”的網頁時,我們會了解到域名解析系統的脆弱。

然而關於DNS還有一大堆故事值得我們去傾聽,去思考。

DNS 源起

要想訪問網路上的一臺計算機,我們必須要知道它的IP地址,但是這些地址(比如243.185.187.39)只是一串數字,沒有規律,因此我們很難記住。並且如果一臺計算機變更IP後,它必須通知所有的人。

顯然,直接使用IP地址是一個愚蠢的方案。於是人們想出了一個替代的方法,即為每一臺計算機起一個名字,然後建立計算機名字到地址的一個對映關係。我們訪問計算機的名字,剩下的名字到地址的轉換過程則由計算機自動完成。

hosts對映

早期,名字到地址的轉換過程十分簡單。每臺計算機儲存一個hosts檔案,裡面列出所有計算機名字和對應的IP地址,然後定期從一個維護此檔案的站點更新裡面的記錄。當我們訪問某個計算機名字時,先在hosts檔案找到對應的IP,然後就可以建立連線。

早期的ARPANET就是這樣做的,但是隨著網路規模的擴大,這種方法漸漸吃不消了。主要有以下三個原因:

  1. hosts檔案變得非常大;

  2. 主機名字會衝突;

  3. 集中的維護站點會不堪重負(需要給幾百萬機器提供hosts檔案,想想就可怕)。

域名系統

為了解決上面的問題,1983年Paul Mockapetris提出了域名系統(DNS, Domain Name System),這是一種層次的、基於域

的命名方案,並且用一個分散式資料庫系統加以實現。當我們需要訪問一個域名(其實就是前面說的計算機的名字)時,應用程式會向DNS伺服器發起一個DNS請求,DNS伺服器返回該域名對應的IP地址。通過下面三種手段解決了上面的問題:

  1. 使用者計算機上並沒有儲存所有的名字到IP的對映,這樣避免了hosts檔案過於龐大(現在各作業系統中hosts檔案預設都是空的)。

  2. 規定了域名的命名規則,保證主機名字不會重複。

  3. DNS伺服器不再是單一的一臺機器,而是一個層次的、合理組織的伺服器叢集。

這樣訪問一個域名的過程可以簡化為下圖:

DNS 協議

那麼如何具體實現這個所謂的域名系統呢,要知道管理一個超大型並且不斷變化的域名到IP的對映集合可不是一個簡單的事,況且還要去應付成千上萬的DNS查詢請求。人們最終想出了一套不錯的協議,規定如何來實現這個系統,下面我們一起來看看吧。

域名空間

首先我們需要制定一套命名規則,防止域名出現重複。DNS關於域名的規則和我們生活中的快遞系統類似,使用層次的地址結構。快遞系統中要給某人郵寄物品,地址可能是這樣:中國、廣東省、廣州市、番禺區、中山西路12號 XXX。而一個域名看起來則是這樣的groups.google.com(為什麼不是com.google.groups?我猜可能和老外寫地址的習慣有關)。

對於Internet來說,域名層次結構的頂級(相當於國際快遞地址中的國家部分)由ICANN(網際網路名稱與數字地址分配機構)負責管理。目前,已經有超過250個頂級域名,每個頂級域名可以進一步劃為一些子域(二級域名),這些子域可被再次劃分(三級域名),依此類推。所有這些域名可以組織成一棵樹,如下圖所示(圖片來自Computer Networks: 7-1 ):

域名資源記錄

DNS設計之初是用來建立域名到IP地址的對映,理論上對於每一個域名我們只需要在域名伺服器上儲存一條記錄即可。這裡的記錄一般叫作域名資源記錄,它是一個五元組,可以用以下格式表示:

Domain_name Time_to_live Class Type Value

其中:

  1. Domain_name: 指出這條記錄適用於哪個域名;

  2. Time_to_live: 用來表明記錄的生存週期,也就是說最多可以快取該記錄多長時間(後面會講到快取機制);

  3. Class: 一般總是IN;

  4. Type: 記錄的型別;

  5. Value: 記錄的值,如果是A記錄,則value是一個IPv4地址。

我們看到域名資源記錄有一個Type欄位,用來表明記錄的型別。這是為什麼呢?因為對於一個域名來說,通常並非只記錄其IP地址,還可能需要一些其他種類的記錄,一些常見的記錄型別如下:

記錄型別含義
A主機的IPv4地址
AAAA主機的IPv6地址
NS該域名所在域的權威域名伺服器
MX接受特定域名電子郵件的伺服器域名
CNAME當前域名的一個別名

關於這些域名資源記錄的例項我們將在下一篇文章(實踐篇)看到。

域名伺服器

我們知道不能只用一臺域名伺服器來響應所有的DNS查詢,因為沒有一臺機器能夠給全球的使用者提供查詢服務,計算能力、儲存、頻寬都不允許。只能合理組織一個域名伺服器叢集,使他們協同工作,共同提供域名解析服務。接下來首先要面對的一個問題是如何合理地將所有的域名資源記錄儲存到不同的域名伺服器上。

前面說過域名的名字空間可以組織為一棵樹,這裡我們可以進一步將其劃分為不重疊的區域(DNS zone),針對上圖的域名空間,一種可能的域名劃分如下圖:

然後將每個區域與多個域名伺服器(其中一個是master,其他slave伺服器則用來提供資料備份、加快解析速度、保證服務可用性)關聯起來,稱這些域名伺服器為該區域的權威域名伺服器(Authoritative Name Servers ),它儲存兩類域名資源記錄:

  1. 該區域內所有域名的域名資源記錄。

  2. 父區域和子區域的域名伺服器對應的域名資源記錄(主要是NS記錄)。

這樣,所有的域名資源記錄都儲存在多個域名伺服器中,並且所有的域名伺服器也組成了一個層次的索引結構,便於我們後面進行域名解析。下面以一個簡化的域名空間為例子,說明域名資源記錄是如何儲存在域名伺服器中的,如下圖a:

圖中域名空間劃分為A, B, C, D, E, F, G七個DNS區域,每個DNS區域都有多個權威域名伺服器,這些域名伺服器裡面儲存了許多域名解析記錄。對於上圖的NDS區域E來說,它的權威域名伺服器裡面儲存的記錄如圖中表格所示。

仔細觀察上圖你可能會發現區域A、B並沒有父區域,他們之間並沒有一條路徑連在一起。這將導致一個很麻煩的問題,那就是區域A的權威域名伺服器可能根本不知道區域B的存在。認識到這一點後,你可能會想出一個很自然的解決方案,就是在A中記錄B域名伺服器的地址,同時在B中記錄A的,這樣它們兩個就聯絡起來了。但是考慮到我們有超過250個頂級域名,這樣做並不是很恰當。

而我們使用的域名系統則採用了一種更加聰明的方法,那就是引入根域名伺服器,它儲存了所有頂級區域的權威域名伺服器記錄。現在通過根域名伺服器,我們可以找到所有的頂級區域的權威域名伺服器,然後就可以往下一級一級找下去了。下圖為全球根域名伺服器的分佈圖,可以在這裡找到。

現在為止,我們的權威域名伺服器和根域名伺服器其實組成了一個樹,樹根為根域名伺服器,下面每個節點都是一個區域的權威域名伺服器,對於圖a中各個DNS區域的權威域名伺服器,它們組成了下面這棵樹(實際中,一個權威域名伺服器可能儲存有多個DNS區域的記錄,因此權威域名伺服器之間的聯絡並不構成一棵樹。這部分的詳細內容可以參考RFC 1034: 4. NAME SERVERS。下面為了容易理解,將其簡化為一棵樹):

域名解析

我們已經有了一個域名伺服器叢集,該叢集合理地儲存了域名空間和域名資源記錄的對應關係。現在我們要做的就是傳送一個DNS請求給域名伺服器,然後坐等它返回正確的域名資源記錄,這個過程叫作域名解析。

嚴格來說,域名解析的過程最早要追溯到建立網路連線。因為每當連線上網路之後,計算機會自動獲得一個預設的DNS伺服器,當然你也可以用自己信任的DNS伺服器,比如8.8.8.8(DNS伺服器也有信任不信任之分,是的,實踐篇會講到),我們把這個域名伺服器也叫作本地域名伺服器。接下來當我們需要知道一個域名對應的資源記錄時,會向本地域名伺服器發起請求,如果該域名恰好在本地域名伺服器所轄屬的域名區域(DNS zone)內,那麼可以直接返回記錄。

如果在本地域名伺服器沒有發現該域名的資源記錄,就需要在整個域名空間搜尋該域名。而整個域名空間的資源記錄儲存在一個分層的、樹狀聯絡的一系列域名伺服器上,所以本地域名伺服器首先要從根域名伺服器開始往下搜尋。這裡有一個問題就是本地域名伺服器如何找到根域名伺服器在哪裡呢?其實域名伺服器啟動的時候,就會載入一個配置檔案,裡面儲存了根域名伺服器的NS記錄(要知道根域名伺服器地址一般非常穩定,不會輕易改變,並且數量很少,所以這個配置檔案會很小)。找到根域名伺服器之後,就可以一級一級地往下查詢啦。

仍然以我們的圖a為例,現在假設區域E內的某個使用者想訪問math.sysu.edu.cn,那麼請求的過程如下:

用語言簡單描述如下:

  1. 使用者:喂,本地域名伺服器,告訴我math.sysu.edu.cn的地址;

  2. 本地域名伺服器:哎呀,我不知道啊,不在我的轄區,容我去問問老大哥吧。root老大,能告訴我math.sysu.edu.cn的地址嗎;

  3. 根域名伺服器:忙著呢,你去問B(.cn);

  4. 本地域名伺服器:喂,B,告訴我math.sysu.edu.cn的地址;

  5. B:你去問D(.edu.cn);

  6. 本地域名伺服器:喂,D,告訴我math.sysu.edu.cn的地址;

  7. D:你去問F(sysu.edu.cn);

  8. 本地域名伺服器:喂,F,告訴我math.sysu.edu.cn的地址;

  9. F:容老衲看看,哎呀,找到了,是X.X.X.X;

  10. 本地域名伺服器:踏破鐵鞋終於找到啦,喂使用者,出來啊,我找到了,是X.X.X.X

仔細想想,這和我們郵寄快遞實在是如出一轍啊,假設你從美國郵東西到廣州市番禺區,首先快遞送到中國(不過這裡沒有一個類似根域名伺服器的中轉站而已),然後往下到廣東省,接下來是廣州市,再往下是番禺了。

上面的是本地域名伺服器的迭代解析過程,其實也可以遞迴查詢,這裡就不說了,道理差不多。

快取機制

現在整個域名系統已經可以為我們提供域名解析服務了,當我們輸入域名,計算機發送DNS請求,然後DNS伺服器返回給我們解析的結果,一切看起來很完美。然而是不是可以更完美呢?

回顧一下平時瀏覽網站的情況,我們會發現兩個比較有意思的結論:

  1. 80%的時間我們都在看那些20%的網站,這就是大名鼎鼎的80/20 Rule

  2. 我們會在一個網站的不同網頁之間跳轉,也就是不斷地訪問同一個域名,類似程式訪問的區域性性原理。

這兩條結論很容易讓我們聯想到快取機制。如果我們將已經訪問過的那些域名的解析結果快取在自己的計算機上,那麼下次訪問的時候可以直接讀取結果,不用再次重複DNS查詢過程,給自己和域名伺服器都節省了麻煩。

當然,這樣做的一個前提是要快取的解析結果不會頻繁更改,也就是說我十分鐘後解析一個域名的結果和現在解析的結果是一樣的。對大多數域名來說,這都是一個不爭的事實。但是難免有一些“善變”的域名,他們可能會頻繁更改自己的解析結果。為了使快取機制適應這兩類情況,我們在域名資源記錄裡面新增一個Time_to_live欄位,表明這條記錄最多可以快取多久。對於那些“穩如泰山”的域名,給一個比較大的值,而那些“朝三暮四”的域名,則可以給定一個小的值。

我們既然可以在本機利用快取,那麼可不可以在域名伺服器上也利用快取機制呢,答案當然是可以的。因為對於域名伺服器來說,上面的兩條有意思的結論仍然有效。所以,域名伺服器可以將那些訪問過的域名資源記錄快取,使用者再次發起請求時,可以直接返回快取結果,不用去迭代或者遞迴解析。

關於DNS理論部分,更多內容還可以參考這兩個文字:

並沒有結束

上面一大堆理論,看上去有點不明所以是吧,沒事,接下來會結合實踐來更加清晰地認識DNS這一最基礎的系統。

其實不止是DNS,還有HTTPS、TCP、UDP這些很基礎的協議,都值得我們靜下心去好好認識它們。因為,寫DNS之前,我以為我已經完全搞明白了它,但是寫的過程發現好多地方自己根本就不知道,之前完全是停留在一個很浮誇的層面上。所以,是時候找時間好好把這些協議過一遍,用自己的語言,從解決問題的角度,記錄下這些經典協議的故事了。