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 (
"fmt"
"time"
"github.com/miekg/dns"
)
var (
msg = new(dns.Msg)
dnsServer = "223.6.6.6:53"
client = new(dns.Client)
)
func ResolveARecord(domain string) error {
// 设置解析超时
client.Timeout = 5 * time.Second
// 设置允许递归查询
msg.RecursionAvailable = true
// 创建 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
}
func main() {
// 要查询的域名
domain := "www.baidu.com"
err := ResolveARecord(domain)
if err != nil {
fmt.Println(err)
}
}
指定源地址
package main
import (
"fmt"
"github.com/miekg/dns"
"net"
)
var (
msg = new(dns.Msg)
dnsServer = "192.168.140.3:53"
client = new(dns.Client)
)
func ResolveARecord(domain string) error {
// 新建本地源地址和端口
dialer := &net.Dialer{
LocalAddr: &net.UDPAddr{
IP: net.ParseIP("192.168.140.90"),
Port: 3333,
},
}
client.Dialer = dialer
// 设置允许递归查询
msg.RecursionAvailable = true
// 创建 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
}
func main() {
// 要查询的域名
domain := "www.baidu.com"
err := ResolveARecord(domain)
if err != nil {
fmt.Println(err)
}
}
也可以不指定端口,不指定端口的情况下,端口会随机产生
使用 EDNS0 Client Subnet
什么是 EDNS0 Client Subnet
EDNS0 (Extension Mechanisms for DNS, version 0) 是一种扩展DNS协议的方式,它允许客户端和服务器之间传输超过原始DNS协议限制的数据量(512字节)。这使得DNS能够支持更多的功能和更复杂的数据交换。
其中,“Client Subnet”(CS)是EDNS0的一个选项,它的主要目的是为了提高DNS解析的地理定位准确性。
Client Subnet选项允许递归解析器(客户端)向权威DNS服务器报告发起请求的客户端的部分IP地址信息。
这样,权威服务器可以根据客户端的大致地理位置来选择最佳的IP地址响应,通常是为了提供更快的服务或遵守地理限制政策。
主要特点:
- 提高性能:通过提供客户端的位置信息,权威服务器可以更好地选择接近客户端的服务器或内容。
- 隐私保护:Client Subnet只发送部分IP地址信息,而不是完整的IP地址,以此减少隐私泄露的风险。
- 地理定位服务:对于需要根据用户地理位置提供服务的情况非常有用,例如CDN(内容分发网络)会选择最近的内容节点。
工作原理:
- 当客户端向递归解析器发送DNS查询时,递归解析器会附加一个包含部分客户端IP地址信息的EDNS0 Client Subnet选项。
- 权威服务器收到这个带有Client Subnet选项的查询后,会使用这些位置信息来决定返回哪个IP地址给客户端。
- 返回的结果通常是离客户端最近的服务器的IP地址,以优化响应时间和用户体验。
总的来说,EDNS0 Client Subnet是一种有效提升DNS解析性能和准确性的技术,尤其适用于那些需要根据用户地理位置来提供服务的应用场景。
使用 EDNS0 Client Subnet
准备一个地址,这里是使用了一个广州移动的ip 211.136.192.6
那么解析的ip,就也是广州移动的IP
前提是 DNS服务是支持 EDNS0 Client Subnet,并且被解析的域名是有配置多地域多线路
package main
import (
"fmt"
"github.com/miekg/dns"
"net"
)
var (
msg = new(dns.Msg)
dnsServer = "223.5.5.5:53"
client = new(dns.Client)
)
func ResolveARecord(domain string) error {
// 开始创建OPT记录
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
ip := net.ParseIP("211.136.192.6")
if ip == nil {
fmt.Println("Invalid IP address")
return fmt.Errorf("Invalid IP address")
}
ecs := new(dns.EDNS0_SUBNET)
if ip.To4() != nil {
ecs = &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
Family: 1, // 1 表示 IPv4,2 表示 IPv6
SourceNetmask: 24,
SourceScope: 0,
Address: ip.To4(),
}
} else {
ecs = &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
Family: 2, // 1 表示 IPv4,2 表示 IPv6
SourceNetmask: 56,
SourceScope: 0,
Address: ip.To16(),
}
}
// 创建EDNS0客户端子网选项
opt.Option = append(opt.Option, ecs)
msg.Extra = append(msg.Extra, opt)
// 设置允许递归查询
msg.RecursionAvailable = true
// 创建 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
}
func main() {
// 要查询的域名
domain := "www.baidu.com"
err := ResolveARecord(domain)
if err != nil {
fmt.Println(err)
}
}
对响应做一些判断
package main
import (
"fmt"
"github.com/miekg/dns"
)
var (
msg = new(dns.Msg)
dnsServer = "223.5.5.5:53"
client = new(dns.Client)
)
func ResolveARecord(domain string) error {
// 设置允许递归查询
msg.RecursionAvailable = true
// 创建 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)
}
// 判断是否来自权威服务器
if response.Authoritative {
fmt.Println("来自权威服务器")
}
// 判断是否递归查询
if response.RecursionAvailable {
fmt.Println("这是递归查询")
}
// 打印 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
}
func main() {
// 要查询的域名
domain := "www.baidu.com"
err := ResolveARecord(domain)
if err != nil {
fmt.Println(err)
}
}
作为服务端
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2023-07-17 Go 标准库net-url
2023-07-17 go中http客户端设置当响应码为3xx时禁止自动跳转
2023-07-17 解决go中http客户端请求遇到tls: server selected unsupported protocol version 301错误
2023-07-17 go中http设置忽略证书
2023-07-17 go中http如何解决跳转自动携带cookie的问题