輕鬆在 Go 中使用 Dot 解析域名

dmls發表於2021-12-04

前言

最近在家無聊翻到了 Dot 跟 Doh 的相關技術文章。所以就想著能否在 Go 中使用自帶的包無侵入性的使用,由於我感覺 Doh 太過於雞肋所以決定看下 Dot

閱讀過 Dot 的一些實現原始碼後知道 Dot 只是對普通的 TCP Dns 請求套上一層 TLS 加密,這大大降低了我的開發難度 (原本以為很複雜)

準備工作

由於 Go 的net標準庫下的元件都有良好的擴充套件性,我決定尋找 Go 是否開放了相關的實現 眾所周知net.Resolver是 Go 中把域名轉換成 IP 的一個元件,它定義了一個 Dial 屬性,通過介紹知道他的主要作用是 用於建立TCP和UDP連線到DNS伺服器,這不就是我想要的嗎!

正式開始

得到了關鍵資訊我決定先寫個列子

net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
    fmt.Println(network,address)
    return nil,io.EOF
}
ip, err := net.LookupIP("www.baidu.com")
if err != nil {
    panic(err)
}
fmt.Println(ip)

按照我想的執行上面的程式碼他會把 DNS 伺服器列印了並且獲取不了www.baidu.com任何 DNS 記錄,但是當我執行後發現Dial中沒有任何輸出並且 DNS 記錄成功的獲取了。why?我帶著疑問翻閱了相關原始碼

func (r *Resolver) lookupIP(ctx context.Context, network, host string) (addrs []IPAddr, err error) {
    if r.preferGo() {
        return r.goLookupIP(ctx, network, host)
    }
    order := systemConf().hostLookupOrder(r, host)
    if order == hostLookupCgo {
        if addrs, err, ok := cgoLookupIP(ctx, network, host); ok {
            return addrs, err
        }
        // cgo not available (or netgo); fall back to Go's DNS resolver
        order = hostLookupFilesDNS
    }
    ips, _, err := r.goLookupIPCNAMEOrder(ctx, network, host, order)
    return ips, err
}

從原始碼中可知如果 preferGo = false && order = hostLookupCgo 就會呼叫 C 函式去獲取 DNS 記錄。閱讀 preferGo 函式的原始碼可得知 net.Resolver定義了一個 PreferGo 的屬性來讓開發者決定用不用 Go 自帶的解析器,預設值會根據系統自動配置具體程式碼在net/conf.go下的initConfVal函式中實現的,由於我是 macos 他會預設關閉,原因可以檢視具體備註

// Darwin pops up annoying dialog boxes if programs try to do
// their own DNS requests. So always use cgo instead, which
// avoids that.
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
    confVal.forceCgoLookupHost = true
    return
}

macos 或者 IOS 會有彈窗出現,我把 PreferGo設定為true後並沒有出現描述的彈窗可能這個彈窗只會在 IOS 上出現。 開啟後再次執行上面的程式碼得到了預期的內容,通過Dial的備註得知如果呼叫後返回的是PacketConn他就會傳送 UDP 資料包其他的則會傳送 TCP 資料包。這裡意味著不需要對 DNS 資料包做任何修改 (UDP 請求中不包含長度,TCP 則會有) 就能達到目的。 這裡我們稍微封裝下就能讓 Go 使用 Dot 進行域名解析

func main() {
    net.DefaultResolver.PreferGo = true

    net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) {
        return NewDnsOverTLSAdopter("223.5.5.5:853", nil)
    }

    ip, err := net.LookupIP("www.baidu.com")
    if err != nil {
        panic(err)
    }
    fmt.Println(ip)
}

func NewDnsOverTLSAdopter(addr string, config *tls.Config) (net.Conn, error) {
    conn, err := tls.Dial("tcp", addr, config)
    if err != nil {
        return nil, err
    }
    if err = conn.Handshake(); err != nil {
        return nil, err
    }
    return conn, nil
}

執行上面的程式碼我們會得到www.baidu.com的 IP[110.242.68.3 110.242.68.4]

注意事項

上面程式碼中的 Dot 伺服器我使用的是 alidns 其實他是個域名但是我沒有使用原因如下

1.會造成程式無限遞迴的去解析域名 2.即使解決了無限遞迴的問題也會造成效能浪費本來 Dot 就已經相比普通 DNS 解析慢了,每次解析一個域名的時候還需要解析另一個域名。

還有不建議像我示例程式碼中那樣對 DefaultResolver 做修改

更多原創文章乾貨分享,請關注公眾號
  • 輕鬆在 Go 中使用 Dot 解析域名
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章