golang實現dns域名解析(一)

  本文將詳細講解如何用go語言一步一步實現dns域名解析的過程,並簡單介紹點dns有關的知識,直接開始正題吧。html

  首先咱們要了解dns解析的過程,沒有了解的請看這裏DNS入門(轉)很詳細。掃盲結束後,咱們須要瞭解下dns報文格式,知道了報文的格式是怎樣的,才能夠寫代碼構造dns請求包:    服務器

  dns請求和應答都是用相同的報文格式,分紅5個段(有的報文段在不一樣的狀況下可能爲空),以下:         數據結構

  

  Header段是報文的頭部,它定義了報文是請求仍是應答,也定義了其餘段是否須要存在,以及是標準查詢仍是其餘。     dom

  Header包含以下字段:函數

         

  各字段分別解釋以下:post

  ID:請求客戶端設置的16位標示,服務器給出應答的時候會帶相同的標示字段回來,這樣請求客戶端就能夠區分不一樣的請求應答了。ui

  QR:1個比特位用來區分是請求(0)仍是應答(1)。編碼

  OPCODE:4個比特位用來設置查詢的種類,應答的時候會帶相同值,可用的值以下: 0 標準查詢 (QUERY) 1 反向查詢 (IQUERY) 2 服務器狀態查詢 (STATUS) 3-15保留值,暫時未使用url

  AA:受權應答(Authoritative Answer) - 這個比特位在應答的時候纔有意義,指出給出應答的服務器是查詢域名的受權解析服務器。注意由於別名的存在,應答可能存在多個主域名,這個AA位對應請求名,或者應答中的第一個主域名。spa

  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長度和空字符串來表示域名結束。注意這個字段可能爲奇數字節,不須要進行邊界填充對齊。   QTYPE2個字節表示查詢類型,.取值能夠爲任何可用的類型值,以及通配碼來表示全部的資源記錄。   QCLASS2個字節表示查詢的協議類,好比,IN表明Internet。因此咱們直接定義查詢的結構體以下:

  type dnsQuery struct {

      QuestionType  uint16

      QuestionClass uint16

  }

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

  剩下的3個段包含相同的格式:一系列可能爲空的資源記錄(RRs)。Answer段包含回答問題的RRs;受權段包含受權域名服務器的RRs;附加段包含和請求相關的,可是不是必須回答的RRs。而在發送請求的時候,咱們是發起請求方,因此這些字段放空就好。

完整代碼:

// 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)
}

 

抓個包看看:

 

這是發出去的,看看詳細的Questions信息:

 

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

相關文章
相關標籤/搜索