golang實現dns網域名稱解析(一)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

  本文將詳細講解如何用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長度和Null 字元串來表示網域名稱結束。注意這個欄位可能為奇數位元組,不需要進行邊界填充對齊。   QTYPE:2個位元組表示查詢類型,.取值可以為任何可用的類型值,以及通配碼來表示所有的資源記錄。   QCLASS:2個位元組表示查詢的協議類,比如,IN代表Internet。所以我們直接定義查詢的結構體如下:

  type dnsQuery struct {

      QuestionType  uint16

      QuestionClass uint16

  }

查詢的網域名稱不定義在查詢的結構體中,由函數接收參數的方式接收。

  剩下的3個段包含相同的格式:一系列可能為空白的資源記錄(RRs)。Answer段包含回答問題的RRs;授權段包含授權網域名稱伺服器的RRs;附加段包含和請求相關的,但是不是必須回答的RRs。而在發送請求的時候,我們是發起請求方,所以這些欄位放空就好。

完整代碼:

// 002 project main.gopackage mainimport (    "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)}

 

抓個包看看:

 

這是發出去的,看看詳細的Questions資訊:

 

我們設定的請求類型是1,class是1,意味著是請求A記錄,class IN。下一節我們在來討論下如何處理伺服器端響應的內容。

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.