實際應用中發現一個問題,在某些國家/ 地區的某些 ISP 提供的網絡中,程序在請求 DNS 以鏈接一些服務器的時候,有時候會由於 ISP 的 DNS 遞歸查詢太慢,致使設備端認爲 DNS 超時了,沒法獲取服務器 IP。html

給用戶的解決方案是:請不要用 ISP 自動分配的 DNS server,改用 8.8.8.8 就解決了。git

可是讓用戶這麼配置太麻煩、也太不友好了。因而我就思考:能不能本身實現 DNS 服務,當 ISP 的 DNS 請求超時或者失敗的時候,就從內部直接向 8.8.8.8 請求 DNS 信息,能夠不?github

gethostbyname()getaddrinfo()/etc/resolve.conf

Reference

DNS 這樣一個在網絡互聯中算是一個比較簡單的協議,實現我如此簡單的需求,竟然沒有哪一個參考資料可以覆蓋我須要的知識點……服務器

我本身也進行了抓包,抓包的時候,建議不直接向權威的 DNS server 發送請求,而是向網關、路由器等提供 DNS 中繼的服務器發,這樣能夠得到比下面最後一個參考資料更多的信息。網絡

《用 TCP / IP 進行網際互聯(第五版)——原理、協議與結構(第五版)》,Douglas E. Comer
《計算機網絡(第5版)》,Andrew S. Tannenbaum, David J. Wetherall:男神塔能鮑姆教授!
DNS Protocol
DNS Reference Information:有各類 type 的說明
Domain Name System (DNS) Parameters:有各類參數的總集合
DNS Name Notation and Message Compression Technique
RFC-1035
對 DNS 報文的理解
DNS message解析:這篇文章也挺仔細地說明了 DNS 報文結構,圖形控能夠看
利用 WireShark 進行 DNS 協議分析異步

DNS 基本概念

簡要整理一些和本文相關的點:socket

DNS 的本質是發明了一種層次的、基於域的命名方案,而且用一個分佈式數據庫系統加以實現。DNS 的主要做用是將主機名映射成 IP 地址。tcp

getaddrinfo()gethostbyname()

DNS 解析中,DNS server 開放的端口應當是 53 端口。當 client 端做出請求時,server 返回的不只僅是 IP 信息,還包含於該域名相關聯的資源記錄。

小於等於 255 個字節小於等於 63 字節

DNS 報文格式

DNS 請求的格式和響應格式差很少,就不單獨講了。從 UDP 數據包的正文部分算起,DNS 報文的結構按順序以下:

uint16_tuint16_tuint16_tuint16_tuint16_tuint16_t
  • Transaction ID:這是由 client 端指定的標識數據,DNS server 會將這個字段原樣返回,client 端能夠用來區分不一樣的 DNS 請求
  • RRResource Record 的縮寫

Flags

16 bits 的值,各部分按順序以下(按順序:位號、Ethereal 名稱、說明):

01234012345678910

資源記錄(RR)的格式

每一條 RR 的格式以下:

uint16_tuint16_tINuint32_tuint16_t

若是是請求數據的話,那麼 TTL、Data Length 和 RR 主數據都不須要

Type
1A2NS28AAAA5CNAME

域名壓縮顯示

這一部分直接參考的是 RFC-1035 的 「4.1.4. Message Compression」小節。

RR 中的 Name 字段,有三種表示方法(不是官方分類,而是本人本身分的):

完整域名錶示

好比表示 「www.google.com」 這樣一個完整的域名,須要如下16個字節:

\3www\6google\3com\0
char *wwwgooglecom\0char *\0

標號表示

位0xC0

這種表示法中,至關於一個指針,指代 DNS 報文中的某一個域名段。在解析一段 RR 數據段時,須要判斷域長度嘛,判斷的邏輯是:

去掉最高兩位
offset = 0x0150

混合表示

www.google.com
0xC020www.google.com0xC024google.com0x016DC0240x6dm0x016Dm0xC024google.comm.google.com

分析工具

除了 Ethereal 以外,推薦的分析工具備:

代碼實現

epoll()
getaddrinfo()
socket()bind()AMCDns_GetDefaultServer()AMCDns_SendRequest()AMCDns_RecvAndResolve()AMCDns_FreeResult()close()