go语言中强大的DNS库--github.com/miekg/dns
简介
github.com/miekg/dns
是一个用于 Go 语言的 DNS 库,它提供了丰富的功能来处理 DNS 查询、响应、解析和构建 DNS 消息。
这个库非常灵活和强大,被广泛用于构建 DNS 客户端和服务器应用程序。
主要功能
- DNS 查询与响应:
- 支持各种类型的 DNS 查询(如 A、AAAA、MX、NS、TXT 等)。
- 支持发送和接收 DNS 消息。
- DNS 消息构建与解析:
- 提供便捷的方法来构建 DNS 消息(如请求和响应)。
- 能够解析 DNS 消息并提取所需信息。
- DNS 服务器:
- 可以用来构建自定义的 DNS 服务器。
- 支持处理多种类型的 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)
}
详细说明
-
注册 DNS 请求处理函数:
dns.HandleFunc(".", handleDNSRequest)
注册一个处理函数
handleDNSRequest
,它将处理所有的 DNS 请求("." 表示所有域名)。 -
设置服务器地址和协议:
server := &dns.Server{Addr: ":53", Net: "udp"}
创建一个 DNS 服务器,监听
:53
端口并使用 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 请求。
-
处理 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
)生成响应消息,并将其发送回客户端。 -
构建 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。
本文来自博客园,作者:厚礼蝎,转载请注明原文链接:https://www.cnblogs.com/guangdelw/p/18306424