go语言中强大的DNS库--github.com/miekg/dns

简介

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。

posted @ 2024-07-17 02:03  厚礼蝎  阅读(17)  评论(0编辑  收藏  举报