go語言中強大的DNS庫--github.com/miekg/dns

厚礼蝎發表於2024-07-17

簡介

github.com/miekg/dns 是一個用於 Go 語言的 DNS 庫,它提供了豐富的功能來處理 DNS 查詢、響應、解析和構建 DNS 訊息。

這個庫非常靈活和強大,被廣泛用於構建 DNS 客戶端和伺服器應用程式。

主要功能

  1. DNS 查詢與響應:
    • 支援各種型別的 DNS 查詢(如 A、AAAA、MX、NS、TXT 等)。
    • 支援傳送和接收 DNS 訊息。
  2. DNS 訊息構建與解析:
    • 提供便捷的方法來構建 DNS 訊息(如請求和響應)。
    • 能夠解析 DNS 訊息並提取所需資訊。
  3. DNS 伺服器:
    • 可以用來構建自定義的 DNS 伺服器。
    • 支援處理多種型別的 DNS 請求。
  4. 擴充套件性:
    • 支援擴充套件和自定義,允許使用者新增自己的功能和處理邏輯。

官網

https://github.com/miekg/dns

安裝

go get github.com/miekg/dns

使用

作為客戶端

獲取各種查詢記錄

package main

import (
	"fmt"

	"github.com/miekg/dns"
)

var (
	msg       = new(dns.Msg)
	dnsServer = "223.6.6.6:53"
	client    = new(dns.Client)
)

// 獲取 A 記錄
func ResolveARecord(domain string) error {
	// 建立 DNS 訊息

	msg.SetQuestion(dns.Fqdn(domain), dns.TypeA)

	// 傳送 DNS 查詢
	response, _, err := client.Exchange(msg, dnsServer)
	if err != nil {
		fmt.Printf("Failed to query DNS for %s: %v\n", domain, err)
		return err
	}

	// 處理響應
	if response.Rcode != dns.RcodeSuccess {
		fmt.Printf("DNS query failed with Rcode %d\n", response.Rcode)
		return fmt.Errorf("DNS query failed with Rcode %d", response.Rcode)
	}

	// 列印 A 記錄
	for _, ans := range response.Answer {
		if aRecord, ok := ans.(*dns.A); ok {
			fmt.Printf("A record for %s: %s\n", domain, aRecord.A.String())
		}
	}
	return nil
}

// 獲取 AAAA 記錄
func ResolveAAAARecord(domain string) error {
	// 建立 DNS 訊息

	msg.SetQuestion(dns.Fqdn(domain), dns.TypeAAAA)

	// 傳送 DNS 查詢
	response, _, err := client.Exchange(msg, dnsServer)
	if err != nil {
		fmt.Printf("Failed to query DNS for %s: %v\n", domain, err)
		return err
	}

	// 處理響應
	if response.Rcode != dns.RcodeSuccess {
		fmt.Printf("DNS query failed with Rcode %d\n", response.Rcode)
		return fmt.Errorf("DNS query failed with Rcode %d", response.Rcode)
	}

	// 列印 AAAA 記錄
	for _, ans := range response.Answer {
		if aRecord, ok := ans.(*dns.AAAA); ok {
			fmt.Printf("AAAA record for %s: %s\n", domain, aRecord.AAAA.String())
		}
	}
	return nil
}

// 獲取 TXT 記錄
func ResolveTXTRecord(domain string) error {
	// 建立 DNS 訊息

	msg.SetQuestion(dns.Fqdn(domain), dns.TypeTXT)

	// 傳送 DNS 查詢
	response, _, err := client.Exchange(msg, dnsServer)
	if err != nil {
		fmt.Printf("Failed to query DNS for %s: %v\n", domain, err)
		return err
	}

	// 處理響應
	if response.Rcode != dns.RcodeSuccess {
		fmt.Printf("DNS query failed with Rcode %d\n", response.Rcode)
		return fmt.Errorf("DNS query failed with Rcode %d", response.Rcode)
	}

	// 列印 TXT 記錄
	for _, ans := range response.Answer {
		if aRecord, ok := ans.(*dns.TXT); ok {
			for _, txt := range aRecord.Txt {
				fmt.Printf("TXT record for %s: %s\n", domain, txt)
			}
		}
	}
	return nil
}

// 獲取 NS 記錄
func ResolveNSRecord(domain string) error {
	// 建立 DNS 訊息

	msg.SetQuestion(dns.Fqdn(domain), dns.TypeNS)

	// 傳送 DNS 查詢
	response, _, err := client.Exchange(msg, dnsServer)
	if err != nil {
		fmt.Printf("Failed to query DNS for %s: %v\n", domain, err)
		return err
	}

	// 處理響應
	if response.Rcode != dns.RcodeSuccess {
		fmt.Printf("DNS query failed with Rcode %d\n", response.Rcode)
		return fmt.Errorf("DNS query failed with Rcode %d", response.Rcode)
	}

	// 列印 TXT 記錄
	for _, ans := range response.Answer {
		if aRecord, ok := ans.(*dns.NS); ok {
			fmt.Printf("NS record for %s: %s\n", domain, aRecord.Ns)
		}
	}
	return nil
}

func main() {
	// 要查詢的域名
	domain := "www.baidu.com"
	ResolveARecord(domain)
	ResolveAAAARecord(domain)
	ResolveTXTRecord(domain)
	ResolveNSRecord(domain)
}

作為服務端

package main

import (
	"log"
	"net"

	"github.com/miekg/dns"
)

func main() {
	// 註冊 DNS 請求處理函式
	dns.HandleFunc(".", handleDNSRequest)

	// 設定伺服器地址和協議
	server := &dns.Server{Addr: ":53", Net: "udp"}
	log.Printf("Starting DNS server on %s\n", server.Addr)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Failed to start DNS server: %v\n", err)
	}
}

// 處理 DNS 請求的函式
func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
	msg := new(dns.Msg)
	msg.SetReply(r)
    // 將 DNS 響應標記為權威應答
	msg.Authoritative = true
    // 將 DNS 響應標記為遞迴可用
    // msg.RecursionAvailable = true
    
	// 遍歷請求中的問題部分,生成相應的回答
	for _, question := range r.Question {
        fmt.Println("請求解析的域名:", question.Name)
		switch question.Qtype {
		case dns.TypeA:
			handleARecord(question, msg)
		case dns.TypeAAAA:
			handleAAAARecord(question, msg)
			// 你可以在這裡新增其他型別的記錄處理邏輯
		}
	}

	w.WriteMsg(msg)
}

// 構建 A 記錄的函式
func handleARecord(q dns.Question, msg *dns.Msg) {
	ip := net.ParseIP("192.0.2.1")
	rr := &dns.A{
		Hdr: dns.RR_Header{
			Name:   q.Name,
			Rrtype: dns.TypeA,
			Class:  dns.ClassINET,
			Ttl:    600,
		},
		A: ip,
	}
	msg.Answer = append(msg.Answer, rr)
}

func handleAAAARecord(q dns.Question, msg *dns.Msg) {
	ip := net.ParseIP("240c::6666")
	rr := &dns.AAAA{
		Hdr: dns.RR_Header{
			Name:   q.Name,
			Rrtype: dns.TypeAAAA,
			Class:  dns.ClassINET,
			Ttl:    600,
		},
		AAAA: ip,
	}
	msg.Answer = append(msg.Answer, rr)
}

詳細說明

  1. 註冊 DNS 請求處理函式

    dns.HandleFunc(".", handleDNSRequest)
    

    註冊一個處理函式 handleDNSRequest,它將處理所有的 DNS 請求("." 表示所有域名)。

  2. 設定伺服器地址和協議

    server := &dns.Server{Addr: ":53", Net: "udp"}
    

    建立一個 DNS 伺服器,監聽 :53 埠並使用 UDP 協議。

  3. 啟動伺服器

    log.Printf("Starting DNS server on %s\n", server.Addr)
    if err := server.ListenAndServe(); err != nil {
        log.Fatalf("Failed to start DNS server: %v\n", err)
    }
    

    啟動伺服器並開始監聽傳入的 DNS 請求。

  4. 處理 DNS 請求的函式

    func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
        msg := new(dns.Msg)
        msg.SetReply(r)
        msg.Authoritative = true
    
        for _, question := range r.Question {
            switch question.Qtype {
            case dns.TypeA:
                handleARecord(question, msg)
            }
        }
    
        w.WriteMsg(msg)
    }
    

    這個函式處理傳入的 DNS 請求。它會根據請求中的問題(r.Question)生成響應訊息,並將其傳送回客戶端。

  5. 構建 A 記錄的函式

    func handleARecord(q dns.Question, msg *dns.Msg) {
        ip := net.ParseIP("192.0.2.1")
        rr := &dns.A{
            Hdr: dns.RR_Header{
                Name:   q.Name,
                Rrtype: dns.TypeA,
                Class:  dns.ClassINET,
                Ttl:    600,
            },
            A: ip,
        }
        msg.Answer = append(msg.Answer, rr)
    }
    

    生成一個 A 記錄的響應,其中 IP 地址為 192.0.2.1,並將其新增到響應訊息中。

處理其他型別的 DNS 記錄

你可以擴充套件 handleDNSRequest 函式來處理其他型別的 DNS 記錄,例如 CNAME、MX、TXT 等。只需在 switch 語句中新增相應的處理函式:

switch question.Qtype {
case dns.TypeA:
    handleARecord(question, msg)
case dns.TypeCNAME:
    handleCNAMERecord(question, msg)
case dns.TypeMX:
    handleMXRecord(question, msg)
case dns.TypeTXT:
    handleTXTRecord(question, msg)
}

然後為每種記錄型別編寫相應的處理函式,例如:

func handleCNAMERecord(q dns.Question, msg *dns.Msg) {
    rr := &dns.CNAME{
        Hdr: dns.RR_Header{
            Name:   q.Name,
            Rrtype: dns.TypeCNAME,
            Class:  dns.ClassINET,
            Ttl:    600,
        },
        Target: "example.com.",
    }
    msg.Answer = append(msg.Answer, rr)
}

func handleMXRecord(q dns.Question, msg *dns.Msg) {
    rr := &dns.MX{
        Hdr: dns.RR_Header{
            Name:   q.Name,
            Rrtype: dns.TypeMX,
            Class:  dns.ClassINET,
            Ttl:    600,
        },
        Preference: 10,
        Mx:         "mail.example.com.",
    }
    msg.Answer = append(msg.Answer, rr)
}

func handleTXTRecord(q dns.Question, msg *dns.Msg) {
    rr := &dns.TXT{
        Hdr: dns.RR_Header{
            Name:   q.Name,
            Rrtype: dns.TypeTXT,
            Class:  dns.ClassINET,
            Ttl:    600,
        },
        Txt: []string{"v=spf1 include:_spf.example.com ~all"},
    }
    msg.Answer = append(msg.Answer, rr)
}

透過這種方式,你可以構建一個功能全面的自定義 DNS 伺服器,能夠處理多種型別的 DNS 查詢並返回相應的響應。

修改或偽造DNS 響應

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/miekg/dns"
)

func main() {
	// 註冊 DNS 請求處理函式
	dns.HandleFunc(".", HandleDNSRequest)

	// 設定伺服器地址和協議
	server := &dns.Server{Addr: ":53", Net: "udp"}
	log.Printf("Starting DNS server on %s\n", server.Addr)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Failed to start DNS server: %v\n", err)
	}
}

func HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
	c := new(dns.Client)
	c.Net = "udp"

	var upstreamDNS string
	if w.RemoteAddr().(*net.UDPAddr).IP.To4() != nil {
		upstreamDNS = "192.168.140.3:53"
	} else {
		upstreamDNS = "[240c::6666]:53"
	}

	resp, _, err := c.Exchange(r, upstreamDNS)
	if err != nil {
		fmt.Printf("查詢上游DNS時出錯: %v\n", err)
		return
	}

	for _, question := range resp.Question {
		fmt.Println("查詢的域名:", question.Name)
		if question.Name == "www.baidu.com." {
			// 修改響應中的 A 和 AAAA 記錄
			for _, answer := range resp.Answer {
				switch rr := answer.(type) {
				case *dns.A:
					fmt.Printf("替換前的A型別地址: %s\n", rr.A.String())
					rr.A = net.ParseIP("1.2.3.4")
					fmt.Printf("替換後的A型別地址: %s\n", rr.A.String())
				case *dns.AAAA:
					fmt.Printf("替換前的AAAA型別地址: %s\n", rr.AAAA.String())
					rr.AAAA = net.ParseIP("::1")
					fmt.Printf("替換後的AAAA型別地址: %s\n", rr.AAAA.String())
				}
			}
		}
	}

	w.WriteMsg(resp)
}

本地監聽在53埠接收DNS請求,並將請求轉發給上游DNS伺服器。
當發現請求解析的域名是www.baidu.com時,會修改響應中的A和AAAA記錄,將其替換為指定的IP地址。
然後返回給客戶端

當然,也可以完全自定義DNS記錄

package main

import (
	"fmt"
	"log"
	"net"
	"strings"

	"github.com/miekg/dns"
)

func main() {
	// 註冊 DNS 請求處理函式
	dns.HandleFunc(".", HandleDNSRequest)

	// 設定伺服器地址和協議
	server := &dns.Server{Addr: ":53", Net: "udp"}
	log.Printf("Starting DNS server on %s\n", server.Addr)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Failed to start DNS server: %v\n", err)
	}
}

func HandleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
	c := new(dns.Client)
	c.Net = "udp"

	var upstreamDNS string
	if w.RemoteAddr().(*net.UDPAddr).IP.To4() != nil {
		upstreamDNS = "192.168.140.3:53"
	} else {
		upstreamDNS = "[240c::6666]:53"
	}

	resp, _, err := c.Exchange(r, upstreamDNS)
	if err != nil {
		fmt.Printf("查詢上游DNS時出錯: %v\n", err)
		return
	}

	var (
		newARecords    = []string{} // 替換的 A 記錄地址
		newAAAARecords = []string{} // 替換的 AAAA 記錄地址
	)
	// 儲存去重後的記錄
	var newAnswers []dns.RR
	host := strings.TrimSuffix(r.Question[0].Name, ".")
	for _, question := range resp.Question {
		fmt.Println("查詢的域名:", question.Name)
		if host == "www.baidu.com" {
			A := false
			AAAA := false
			for _, answer := range resp.Answer {
				switch rr := answer.(type) {
				case *dns.A:
					fmt.Println("解析的v4原地址:", rr.A.String())
					if A {
						continue
					}
					A = true
				case *dns.AAAA:
					fmt.Println("解析的v6原地址:", rr.AAAA.String())
					if AAAA {
						continue
					}
					AAAA = true
				}
			}
			if A {
				newARecords = append(newARecords, "192.168.1.2")
				// 處理 A 記錄
				for _, addr := range newARecords {
					newRR := &dns.A{
						Hdr: dns.RR_Header{
							Name:   r.Question[0].Name,
							Rrtype: dns.TypeA,
							Class:  dns.ClassINET,
							Ttl:    300,
						},
						A: net.ParseIP(addr),
					}
					newAnswers = append(newAnswers, newRR)
				}
				// 替換響應中的記錄
				resp.Answer = newAnswers
			}
			if AAAA {
				newAAAARecords = append(newAAAARecords, "2001:1:2:3:4::5")
				// 處理 AAAA 記錄
				for _, addr := range newAAAARecords {
					newRR := &dns.AAAA{
						Hdr: dns.RR_Header{
							Name:   r.Question[0].Name,
							Rrtype: dns.TypeAAAA,
							Class:  dns.ClassINET,
							Ttl:    300,
						},
						AAAA: net.ParseIP(addr),
					}
					newAnswers = append(newAnswers, newRR)
				}
				// 替換響應中的記錄
				resp.Answer = newAnswers
			}
		}
	}

	w.WriteMsg(resp)
}

這裡實現了一個 DNS 伺服器,透過 HandleDNSRequest 函式處理傳入的 DNS 請求,並根據請求的域名和型別替換響應中的記錄。
如果查詢的是 www.baidu.com,且存在 A 記錄或 AAAA 記錄,則將這些記錄分別替換為偽造的 IP 地址 192.168.1.2 和 2001:1:2:3:4::5。

相關文章