golang实现dns域名解析(一)
本文將詳細(xì)講解如何用go語(yǔ)言一步一步實(shí)現(xiàn)dns域名解析的過(guò)程,并簡(jiǎn)單介紹點(diǎn)dns有關(guān)的知識(shí),直接開(kāi)始正題吧。
首先我們要了解dns解析的過(guò)程,沒(méi)有了解的請(qǐng)看這里DNS入門(mén)(轉(zhuǎn))很詳細(xì)。掃盲結(jié)束后,我們需要了解下dns報(bào)文格式,知道了報(bào)文的格式是怎樣的,才可以寫(xiě)代碼構(gòu)造dns請(qǐng)求包: ???
dns請(qǐng)求和應(yīng)答都是用相同的報(bào)文格式,分成5個(gè)段(有的報(bào)文段在不同的情況下可能為空),如下: ???? ???
Header段是報(bào)文的頭部,它定義了報(bào)文是請(qǐng)求還是應(yīng)答,也定義了其他段是否需要存在,以及是標(biāo)準(zhǔn)查詢(xún)還是其他。 ????
Header包含如下字段:
??????
各字段分別解釋如下:
ID:請(qǐng)求客戶(hù)端設(shè)置的16位標(biāo)示,服務(wù)器給出應(yīng)答的時(shí)候會(huì)帶相同的標(biāo)示字段回來(lái),這樣請(qǐng)求客戶(hù)端就可以區(qū)分不同的請(qǐng)求應(yīng)答了。
QR:1個(gè)比特位用來(lái)區(qū)分是請(qǐng)求(0)還是應(yīng)答(1)。
OPCODE:4個(gè)比特位用來(lái)設(shè)置查詢(xún)的種類(lèi),應(yīng)答的時(shí)候會(huì)帶相同值,可用的值如下: 0?標(biāo)準(zhǔn)查詢(xún) (QUERY) 1?反向查詢(xún) (IQUERY) 2 服務(wù)器狀態(tài)查詢(xún) (STATUS) 3-15保留值,暫時(shí)未使用
AA:授權(quán)應(yīng)答(Authoritative Answer) - 這個(gè)比特位在應(yīng)答的時(shí)候才有意義,指出給出應(yīng)答的服務(wù)器是查詢(xún)域名的授權(quán)解析服務(wù)器。注意因?yàn)閯e名的存在,應(yīng)答可能存在多個(gè)主域名,這個(gè)AA位對(duì)應(yīng)請(qǐng)求名,或者應(yīng)答中的第一個(gè)主域名。
TC:截?cái)?/span>(TrunCation) - 用來(lái)指出報(bào)文比允許的長(zhǎng)度還要長(zhǎng),導(dǎo)致被截?cái)唷???
RD:期望遞歸(Recursion Desired) - 這個(gè)比特位被請(qǐng)求設(shè)置,應(yīng)答的時(shí)候使用的相同的值返回。如果設(shè)置了RD,就建議域名服務(wù)器進(jìn)行遞歸解析,遞歸查詢(xún)的支持是可選的。 ??
RA:支持遞歸(Recursion Available) - 這個(gè)比特位在應(yīng)答中設(shè)置或取消,用來(lái)代表服務(wù)器是否支持遞歸查詢(xún)。 ??
Z:保留值,暫時(shí)未使用。在所有的請(qǐng)求和應(yīng)答報(bào)文中必須置為0。 ??
RCODE:應(yīng)答碼(Response code) - 這4個(gè)比特位在應(yīng)答報(bào)文中設(shè)置,代表的含義如下:
0?沒(méi)有錯(cuò)誤。
1?報(bào)文格式錯(cuò)誤(Format error) - 服務(wù)器不能理解請(qǐng)求的報(bào)文。
2?服務(wù)器失敗(Server failure) - 因?yàn)榉?wù)器的原因?qū)е聸](méi)辦法處理這個(gè)請(qǐng)求。
3?名字錯(cuò)誤(Name Error) - 只有對(duì)授權(quán)域名解析服務(wù)器有意義,指出解析的域名不存在。
4?沒(méi)有實(shí)現(xiàn)(Not Implemented) - 域名服務(wù)器不支持查詢(xún)類(lèi)型。
5?拒絕(Refused) - 服務(wù)器由于設(shè)置的策略拒絕給出應(yīng)答。比如,服務(wù)器不希望對(duì)某些請(qǐng)求者給出應(yīng)答,或者服務(wù)器不希望進(jìn)行某些操作(比如區(qū)域傳送zone transfer)。
6-15?保留值,暫時(shí)未使用。
QDCOUNT?無(wú)符號(hào)16位整數(shù)表示報(bào)文請(qǐng)求段中的問(wèn)題記錄數(shù)。
ANCOUNT?無(wú)符號(hào)16位整數(shù)表示報(bào)文回答段中的回答記錄數(shù)。
NSCOUNT?無(wú)符號(hào)16位整數(shù)表示報(bào)文授權(quán)段中的授權(quán)記錄數(shù)。
ARCOUNT?無(wú)符號(hào)16位整數(shù)表示報(bào)文附加段中的附加記錄數(shù)。
根據(jù)這些,dns頭部的數(shù)據(jù)結(jié)構(gòu)可以定義如下:
type dnsHeader struct {
??? ?Id ????????????????????????????????uint16
?? ??Bits ??????????????????????????????uint16
? ???Qdcount, Ancount, Nscount, Arcount uint16
}
構(gòu)造頭部信息我們主要處理Bits,可以直接根據(jù)需求對(duì)相應(yīng)位置值,也可以定義好每一個(gè)字段,通過(guò)移位的方式然后相加構(gòu)造請(qǐng)求的頭部各個(gè)字段。推薦后一種方法,這樣就有:
???? header.Bits = QR<<15 + OperationCode<<11 + AuthoritativeAnswer<<10 + Truncation<<9 + RecursionDesired<<8 + RecursionAvailable<<7 + ResponseCode
????其他的頭部信息就比較簡(jiǎn)單了:
requestHeader := dnsHeader{
????????Id: ?????0x0010,
????????Qdcount: 1,
????????Ancount: 0,
????????Nscount: 0,
????????Arcount: 0,
}
報(bào)文頭搞定后,接下來(lái)就是查詢(xún)問(wèn)題Question:
Question段描述了查詢(xún)的問(wèn)題,包括查詢(xún)類(lèi)型(QTYPE),查詢(xún)類(lèi)(QCLASS),以及查詢(xún)的域名(QNAME)。字段含義如下 ??QNAME:域名被編碼為一些labels序列,每個(gè)labels包含一個(gè)字節(jié)表示后續(xù)字符串長(zhǎng)度,以及這個(gè)字符串,以0長(zhǎng)度和空字符串來(lái)表示域名結(jié)束。注意這個(gè)字段可能為奇數(shù)字節(jié),不需要進(jìn)行邊界填充對(duì)齊。 ??QTYPE:2個(gè)字節(jié)表示查詢(xún)類(lèi)型,.取值可以為任何可用的類(lèi)型值,以及通配碼來(lái)表示所有的資源記錄。 ??QCLASS:2個(gè)字節(jié)表示查詢(xún)的協(xié)議類(lèi),比如,IN代表Internet。所以我們直接定義查詢(xún)的結(jié)構(gòu)體如下:
type dnsQuery struct {
???? QuestionType ?uint16
???? QuestionClass uint16
}
查詢(xún)的域名不定義在查詢(xún)的結(jié)構(gòu)體中,由函數(shù)接收參數(shù)的方式接收。
剩下的3個(gè)段包含相同的格式:一系列可能為空的資源記錄(RRs)。Answer段包含回答問(wèn)題的RRs;授權(quán)段包含授權(quán)域名服務(wù)器的RRs;附加段包含和請(qǐng)求相關(guān)的,但是不是必須回答的RRs。而在發(fā)送請(qǐng)求的時(shí)候,我們是發(fā)起請(qǐng)求方,所以這些字段放空就好。
完整代碼:
// 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) }?
抓個(gè)包看看:
?
這是發(fā)出去的,看看詳細(xì)的Questions信息:
?
我們?cè)O(shè)置的請(qǐng)求類(lèi)型是1,class是1,意味著是請(qǐng)求A記錄,class IN。下一節(jié)我們?cè)趤?lái)討論下如何處理服務(wù)器端響應(yīng)的內(nèi)容。
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/chase-wind/p/6814053.html
總結(jié)
以上是生活随笔為你收集整理的golang实现dns域名解析(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 军队医院全部会转到融通吗?聘用人员待遇?
- 下一篇: canvas中strokeRect的渲染