簡介
github.com/miekg/dns
是一個用於 Go 語言的 DNS 庫,它提供了豐富的功能來處理 DNS 查詢、響應、解析和構建 DNS 訊息。
這個庫非常靈活和強大,被廣泛用於構建 DNS 客戶端和伺服器應用程式。
主要功能
- DNS 查詢與響應:
- 支援各種型別的 DNS 查詢(如 A、AAAA、MX、NS、TXT 等)。
- 支援傳送和接收 DNS 訊息。
- DNS 訊息構建與解析:
- 提供便捷的方法來構建 DNS 訊息(如請求和響應)。
- 能夠解析 DNS 訊息並提取所需資訊。
- DNS 伺服器:
- 可以用來構建自定義的 DNS 伺服器。
- 支援處理多種型別的 DNS 請求。
- 擴充套件性:
- 支援擴充套件和自定義,允許使用者新增自己的功能和處理邏輯。
官網
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)
}
詳細說明
-
註冊 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。