golang实现dns域名解析(一)
本文將詳細講解如何用go語言一步一步實現dns域名解析的過程,并簡單介紹點dns有關的知識,直接開始正題吧。
首先我們要了解dns解析的過程,沒有了解的請看這里DNS入門(轉)很詳細。掃盲結束后,我們需要了解下dns報文格式,知道了報文的格式是怎樣的,才可以寫代碼構造dns請求包: ???
dns請求和應答都是用相同的報文格式,分成5個段(有的報文段在不同的情況下可能為空),如下: ???? ???
Header段是報文的頭部,它定義了報文是請求還是應答,也定義了其他段是否需要存在,以及是標準查詢還是其他。 ????
Header包含如下字段:
??????
各字段分別解釋如下:
ID:請求客戶端設置的16位標示,服務器給出應答的時候會帶相同的標示字段回來,這樣請求客戶端就可以區分不同的請求應答了。
QR:1個比特位用來區分是請求(0)還是應答(1)。
OPCODE:4個比特位用來設置查詢的種類,應答的時候會帶相同值,可用的值如下: 0?標準查詢 (QUERY) 1?反向查詢 (IQUERY) 2 服務器狀態查詢 (STATUS) 3-15保留值,暫時未使用
AA:授權應答(Authoritative Answer) - 這個比特位在應答的時候才有意義,指出給出應答的服務器是查詢域名的授權解析服務器。注意因為別名的存在,應答可能存在多個主域名,這個AA位對應請求名,或者應答中的第一個主域名。
TC:截斷(TrunCation) - 用來指出報文比允許的長度還要長,導致被截斷。 ??
RD:期望遞歸(Recursion Desired) - 這個比特位被請求設置,應答的時候使用的相同的值返回。如果設置了RD,就建議域名服務器進行遞歸解析,遞歸查詢的支持是可選的。 ??
RA:支持遞歸(Recursion Available) - 這個比特位在應答中設置或取消,用來代表服務器是否支持遞歸查詢。 ??
Z:保留值,暫時未使用。在所有的請求和應答報文中必須置為0。 ??
RCODE:應答碼(Response code) - 這4個比特位在應答報文中設置,代表的含義如下:
0?沒有錯誤。
1?報文格式錯誤(Format error) - 服務器不能理解請求的報文。
2?服務器失敗(Server failure) - 因為服務器的原因導致沒辦法處理這個請求。
3?名字錯誤(Name Error) - 只有對授權域名解析服務器有意義,指出解析的域名不存在。
4?沒有實現(Not Implemented) - 域名服務器不支持查詢類型。
5?拒絕(Refused) - 服務器由于設置的策略拒絕給出應答。比如,服務器不希望對某些請求者給出應答,或者服務器不希望進行某些操作(比如區域傳送zone transfer)。
6-15?保留值,暫時未使用。
QDCOUNT?無符號16位整數表示報文請求段中的問題記錄數。
ANCOUNT?無符號16位整數表示報文回答段中的回答記錄數。
NSCOUNT?無符號16位整數表示報文授權段中的授權記錄數。
ARCOUNT?無符號16位整數表示報文附加段中的附加記錄數。
根據這些,dns頭部的數據結構可以定義如下:
type dnsHeader struct {
??? ?Id ????????????????????????????????uint16
?? ??Bits ??????????????????????????????uint16
? ???Qdcount, Ancount, Nscount, Arcount uint16
}
構造頭部信息我們主要處理Bits,可以直接根據需求對相應位置值,也可以定義好每一個字段,通過移位的方式然后相加構造請求的頭部各個字段。推薦后一種方法,這樣就有:
???? header.Bits = QR<<15 + OperationCode<<11 + AuthoritativeAnswer<<10 + Truncation<<9 + RecursionDesired<<8 + RecursionAvailable<<7 + ResponseCode
????其他的頭部信息就比較簡單了:
requestHeader := dnsHeader{
????????Id: ?????0x0010,
????????Qdcount: 1,
????????Ancount: 0,
????????Nscount: 0,
????????Arcount: 0,
}
報文頭搞定后,接下來就是查詢問題Question:
Question段描述了查詢的問題,包括查詢類型(QTYPE),查詢類(QCLASS),以及查詢的域名(QNAME)。字段含義如下 ??QNAME:域名被編碼為一些labels序列,每個labels包含一個字節表示后續字符串長度,以及這個字符串,以0長度和空字符串來表示域名結束。注意這個字段可能為奇數字節,不需要進行邊界填充對齊。 ??QTYPE:2個字節表示查詢類型,.取值可以為任何可用的類型值,以及通配碼來表示所有的資源記錄。 ??QCLASS:2個字節表示查詢的協議類,比如,IN代表Internet。所以我們直接定義查詢的結構體如下:
type dnsQuery struct {
???? QuestionType ?uint16
???? QuestionClass uint16
}
查詢的域名不定義在查詢的結構體中,由函數接收參數的方式接收。
剩下的3個段包含相同的格式:一系列可能為空的資源記錄(RRs)。Answer段包含回答問題的RRs;授權段包含授權域名服務器的RRs;附加段包含和請求相關的,但是不是必須回答的RRs。而在發送請求的時候,我們是發起請求方,所以這些字段放空就好。
完整代碼:
// 002 project main.go package mainimport ("bytes""encoding/binary""fmt""net""strings""time" )type dnsHeader struct {Id uint16Bits uint16Qdcount, Ancount, Nscount, Arcount uint16 }func (header *dnsHeader) SetFlag(QR uint16, OperationCode uint16, AuthoritativeAnswer uint16, Truncation uint16, RecursionDesired uint16, RecursionAvailable uint16, ResponseCode uint16) {header.Bits = QR<<15 + OperationCode<<11 + AuthoritativeAnswer<<10 + Truncation<<9 + RecursionDesired<<8 + RecursionAvailable<<7 + ResponseCode }type dnsQuery struct {QuestionType uint16QuestionClass uint16 }func ParseDomainName(domain string) []byte {var (buffer bytes.Buffersegments []string = strings.Split(domain, "."))for _, seg := range segments {binary.Write(&buffer, binary.BigEndian, byte(len(seg)))binary.Write(&buffer, binary.BigEndian, []byte(seg))}binary.Write(&buffer, binary.BigEndian, byte(0x00))return buffer.Bytes() } func Send(dnsServer, domain string) ([]byte, int, time.Duration) {requestHeader := dnsHeader{Id: 0x0010,Qdcount: 1,Ancount: 0,Nscount: 0,Arcount: 0,}requestHeader.SetFlag(0, 0, 0, 0, 1, 0, 0)requestQuery := dnsQuery{QuestionType: 1,QuestionClass: 1,}var (conn net.Connerr errorbuffer bytes.Buffer)if conn, err = net.Dial("udp", dnsServer); err != nil {fmt.Println(err.Error())return make([]byte, 0), 0, 0}defer conn.Close()binary.Write(&buffer, binary.BigEndian, requestHeader)binary.Write(&buffer, binary.BigEndian, ParseDomainName(domain))binary.Write(&buffer, binary.BigEndian, requestQuery)buf := make([]byte, 1024)t1 := time.Now()if _, err := conn.Write(buffer.Bytes()); err != nil {fmt.Println(err.Error())return make([]byte, 0), 0, 0}length, err := conn.Read(buf)t := time.Now().Sub(t1)return buf, length, t } func main() {remsg, n, _ := Send("114.114.114.114:53", "www.baidu.com")fmt.Println(remsg, n) }?
抓個包看看:
?
這是發出去的,看看詳細的Questions信息:
?
我們設置的請求類型是1,class是1,意味著是請求A記錄,class IN。下一節我們在來討論下如何處理服務器端響應的內容。
?
?
?
轉載于:https://www.cnblogs.com/chase-wind/p/6814053.html
總結
以上是生活随笔為你收集整理的golang实现dns域名解析(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 军队医院全部会转到融通吗?聘用人员待遇?
- 下一篇: 5.45×39的7n39钨芯穿甲弹能击穿