本文將詳細(xì)講解如何用go語言一步一步實(shí)現(xiàn)dns域名解析的過程,并簡單介紹點(diǎn)dns有關(guān)的知識,直接開始正題吧,。 首先我們要了解dns解析的過程,,沒有了解的請看這里DNS入門(轉(zhuǎn))很詳細(xì),。掃盲結(jié)束后,我們需要了解下dns報文格式,,知道了報文的格式是怎樣的,,才可以寫代碼構(gòu)造dns請求包: dns請求和應(yīng)答都是用相同的報文格式,分成5個段(有的報文段在不同的情況下可能為空),,如下:
Header段是報文的頭部,,它定義了報文是請求還是應(yīng)答,也定義了其他段是否需要存在,,以及是標(biāo)準(zhǔn)查詢還是其他,。 Header包含如下字段:
各字段分別解釋如下: ID:請求客戶端設(shè)置的16位標(biāo)示,服務(wù)器給出應(yīng)答的時候會帶相同的標(biāo)示字段回來,,這樣請求客戶端就可以區(qū)分不同的請求應(yīng)答了,。 QR:1個比特位用來區(qū)分是請求(0)還是應(yīng)答(1)。 OPCODE:4個比特位用來設(shè)置查詢的種類,,應(yīng)答的時候會帶相同值,,可用的值如下: 0 標(biāo)準(zhǔn)查詢 (QUERY) 1 反向查詢 (IQUERY) 2 服務(wù)器狀態(tài)查詢 (STATUS) 3-15保留值,暫時未使用 AA:授權(quán)應(yīng)答(Authoritative Answer) - 這個比特位在應(yīng)答的時候才有意義,,指出給出應(yīng)答的服務(wù)器是查詢域名的授權(quán)解析服務(wù)器,。注意因?yàn)閯e名的存在,應(yīng)答可能存在多個主域名,,這個AA位對應(yīng)請求名,,或者應(yīng)答中的第一個主域名。 TC:截斷(TrunCation) - 用來指出報文比允許的長度還要長,,導(dǎo)致被截斷,。 RD:期望遞歸(Recursion Desired) - 這個比特位被請求設(shè)置,應(yīng)答的時候使用的相同的值返回,。如果設(shè)置了RD,,就建議域名服務(wù)器進(jìn)行遞歸解析,遞歸查詢的支持是可選的,。 RA:支持遞歸(Recursion Available) - 這個比特位在應(yīng)答中設(shè)置或取消,,用來代表服務(wù)器是否支持遞歸查詢。 Z:保留值,,暫時未使用,。在所有的請求和應(yīng)答報文中必須置為0。 RCODE:應(yīng)答碼(Response code) - 這4個比特位在應(yīng)答報文中設(shè)置,,代表的含義如下: 0 沒有錯誤,。 1 報文格式錯誤(Format error) - 服務(wù)器不能理解請求的報文。 2 服務(wù)器失敗(Server failure) - 因?yàn)榉?wù)器的原因?qū)е聸]辦法處理這個請求,。 3 名字錯誤(Name Error) - 只有對授權(quán)域名解析服務(wù)器有意義,,指出解析的域名不存在。 4 沒有實(shí)現(xiàn)(Not Implemented) - 域名服務(wù)器不支持查詢類型,。 5 拒絕(Refused) - 服務(wù)器由于設(shè)置的策略拒絕給出應(yīng)答,。比如,服務(wù)器不希望對某些請求者給出應(yīng)答,,或者服務(wù)器不希望進(jìn)行某些操作(比如區(qū)域傳送zone transfer),。 6-15 保留值,暫時未使用,。 QDCOUNT 無符號16位整數(shù)表示報文請求段中的問題記錄數(shù),。 ANCOUNT 無符號16位整數(shù)表示報文回答段中的回答記錄數(shù)。 NSCOUNT 無符號16位整數(shù)表示報文授權(quán)段中的授權(quán)記錄數(shù),。 ARCOUNT 無符號16位整數(shù)表示報文附加段中的附加記錄數(shù),。 根據(jù)這些,dns頭部的數(shù)據(jù)結(jié)構(gòu)可以定義如下: type dnsHeader struct { Id uint16 Bits uint16 Qdcount, Ancount, Nscount, Arcount uint16 } 構(gòu)造頭部信息我們主要處理Bits,,可以直接根據(jù)需求對相應(yīng)位置值,,也可以定義好每一個字段,通過移位的方式然后相加構(gòu)造請求的頭部各個字段,。推薦后一種方法,,這樣就有: 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包含一個字節(jié)表示后續(xù)字符串長度,以及這個字符串,,以0長度和空字符串來表示域名結(jié)束,。注意這個字段可能為奇數(shù)字節(jié),不需要進(jìn)行邊界填充對齊,。 QTYPE:2個字節(jié)表示查詢類型,,.取值可以為任何可用的類型值,以及通配碼來表示所有的資源記錄,。 QCLASS:2個字節(jié)表示查詢的協(xié)議類,,比如,IN代表Internet,。所以我們直接定義查詢的結(jié)構(gòu)體如下: type dnsQuery struct { QuestionType uint16 QuestionClass uint16 } 查詢的域名不定義在查詢的結(jié)構(gòu)體中,,由函數(shù)接收參數(shù)的方式接收,。 剩下的3個段包含相同的格式:一系列可能為空的資源記錄(RRs)。Answer段包含回答問題的RRs,;授權(quán)段包含授權(quán)域名服務(wù)器的RRs,;附加段包含和請求相關(guān)的,但是不是必須回答的RRs,。而在發(fā)送請求的時候,,我們是發(fā)起請求方,所以這些字段放空就好,。 完整代碼: // 002 project main.go package main import ( "bytes" "encoding/binary" "fmt" "net" "strings" "time" ) type dnsHeader struct { Id uint16 Bits uint16 Qdcount, 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 uint16 QuestionClass uint16 } func ParseDomainName(domain string) []byte { var ( buffer bytes.Buffer segments []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.Conn err error buffer 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) }
抓個包看看:
這是發(fā)出去的,,看看詳細(xì)的Questions信息:
我們設(shè)置的請求類型是1,class是1,意味著是請求A記錄,,class IN,。下一節(jié)我們在來討論下如何處理服務(wù)器端響應(yīng)的內(nèi)容。
轉(zhuǎn)自:http://www.cnblogs.com/chase-wind/p/6814053.html |
|