實際應用中發現一個問題,在某些國家/ 地區的某些 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 請求
- RR:Resource 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()