使用Golang通过chan转发DNS请求实现DNS代理
使用Golang通过chan转发DNS请求实现DNS代理 我想在我的另一个应用中实现一个DNS代理包。我打算通过更改DNS服务器地址来实现。我会将其更改为127.0.0.1,并在本地主机上监听,当某些应用发出请求时,我会通过使用真实的WiFi发送请求,然后将DNS响应传输到本地主机上的应用。
我可以通过WiFi发送请求,并且可以用运行在本地主机上的DNS服务器进行响应,但存在一些问题。
在我收到DNS响应数据包后,我想通过通道将其发送到DNS服务器函数(至少我认为可以这样做),然后DNS服务器将响应应用。
我想到的问题如下:
- 如果一个请求进来,在应用响应之前另一个请求也进来了,通道会改变,我就无法正确响应。
- 如果一个请求进来,然后我将该请求转发给服务器,但如果没有响应,我就无法监听其他请求。
这是我的代码:
func main() {
server := &dns.Server{Addr: ":53", Net: "udp"}
go server.ListenAndServe()
dns.HandleFunc(".", handleRequest)
ListenToWifi()
select {}
}
我在这里使用了 miekg/dns 包。
当请求进来时,handleRequest 函数会工作。
我通过 ListenToWifi() 函数监听WiFi。
func handleRequest(w dns.ResponseWriter, req *dns.Msg) {
resp := new(dns.Msg)
resp.SetReply(req)
SendPacketFromLocalHost(req.Id, req.Question[0].Name)
w.WriteMsg(resp)
}
func SendPacketFromLocalHost(id uint16, domain string) {
// 打开设备
handle, err := pcap.OpenLive(`\Device\NPF_Loopback`, 1024, false, 0)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
SendPacket(handle, id, domain)
}
func ListenToWifi() {
// 打开设备
handle, err := pcap.OpenLive(`\Device\NPF_{<Device-Hardw-Id>}`, 2048, false, 100*time.Millisecond)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// UDP 层
udpLayer := packet.Layer(layers.LayerTypeUDP)
if udpLayer == nil {
continue
}
_, ok := udpLayer.(*layers.UDP)
if !ok {
continue
}
// DNS 层
dnsLayer := packet.Layer(layers.LayerTypeDNS)
if dnsLayer == nil {
continue
}
dns, ok := dnsLayer.(*layers.DNS)
if !ok {
continue
}
domainName := string([]byte(dns.Questions[0].Name))
log.Printf("%s 0x%x \n", domainName, dns.ID)
}
}
func SendPacket(handle *pcap.Handle, dnsID uint16, domainNames ...string) {
// 用于环回接口
lo := layers.Loopback{
Family: layers.ProtocolFamilyIPv4,
}
// 创建 IP 层
ip := layers.IPv4{
Version: 4,
TTL: 128,
SrcIP: net.IP{192, 168, 20, 55}, // 路由器 IP
DstIP: net.IP{192, 168, 20, 1}, // 我的 IP
Protocol: layers.IPProtocolUDP,
}
// 创建 UDP 层
udp := layers.UDP{
SrcPort: 65444,
DstPort: 53,
}
udp.SetNetworkLayerForChecksum(&ip)
questions := []layers.DNSQuestion{}
for _, d := range domainNames {
qst := layers.DNSQuestion{
Name: []byte(d),
Type: layers.DNSTypeA,
Class: layers.DNSClassIN,
}
questions = append(questions, qst)
}
dns := layers.DNS{
BaseLayer: layers.BaseLayer{},
ID: dnsID,
QR: false,
OpCode: 0,
AA: false,
TC: false,
RD: true,
RA: true,
Z: 0,
ResponseCode: 0,
QDCount: 1,
ANCount: 0,
NSCount: 0,
ARCount: 0,
Questions: questions,
}
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err = gopacket.SerializeLayers(buffer, options,
&lo,
&ip,
&udp,
&dns,
); err != nil {
fmt.Printf("[-] 序列化错误: %s\n", err.Error())
return
}
outgoingPacket := buffer.Bytes()
if err = handle.WritePacketData(outgoingPacket); err != nil {
fmt.Printf("[-] 发送时出错: %s\n", err.Error())
return
}
}
我原本打算使用通道,当请求进来时,我打算在 handleRequest() 中通过通道发送域名,在我收到响应后,再将响应发送给应用。
你们知道如何实现这个吗? 感谢帮助。
更多关于使用Golang通过chan转发DNS请求实现DNS代理的实战教程也可以访问 https://www.itying.com/category-94-b0.html
如果您使用代理服务器以这种格式导出会很有帮助。有多种代理可供选择,包括免费代理、匿名代理和精英代理。如果您使用代理的目的是为了防止您的解析器被网站屏蔽,那么使用 https://soax.com/india-proxy 是最佳选择。您将变得像普通用户一样,不知道网站的代理是什么。此外,一项额外的防封禁措施是轮换用户代理,即每次发送一个变化的伪造请求头,告诉对方您是一个常规浏览器。
更多关于使用Golang通过chan转发DNS请求实现DNS代理的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
要实现通过通道转发DNS请求的代理,关键在于正确管理请求ID和响应通道的映射关系。以下是改进后的实现方案:
package main
import (
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/miekg/dns"
"github.com/google/gopacket/pcap"
)
type DNSRequest struct {
ID uint16
Response chan *layers.DNS
Timeout time.Time
}
type DNSProxy struct {
requests map[uint16]*DNSRequest
requestsLock sync.RWMutex
requestChan chan *dns.Msg
responseChan chan *layers.DNS
}
func NewDNSProxy() *DNSProxy {
return &DNSProxy{
requests: make(map[uint16]*DNSRequest),
requestChan: make(chan *dns.Msg, 100),
responseChan: make(chan *layers.DNS, 100),
}
}
func (p *DNSProxy) handleRequest(w dns.ResponseWriter, req *dns.Msg) {
// 将请求放入通道,异步处理
p.requestChan <- req
// 创建响应通道
respChan := make(chan *layers.DNS, 1)
p.requestsLock.Lock()
p.requests[req.Id] = &DNSRequest{
ID: req.Id,
Response: respChan,
Timeout: time.Now().Add(5 * time.Second),
}
p.requestsLock.Unlock()
// 等待响应或超时
select {
case dnsResp := <-respChan:
// 将layers.DNS转换为dns.Msg
resp := p.convertToDNSMsg(dnsResp)
w.WriteMsg(resp)
case <-time.After(5 * time.Second):
// 返回超时响应
resp := new(dns.Msg)
resp.SetRcode(req, dns.RcodeServerFailure)
w.WriteMsg(resp)
}
// 清理
p.requestsLock.Lock()
delete(p.requests, req.Id)
p.requestsLock.Unlock()
}
func (p *DNSProxy) StartRequestProcessor() {
go func() {
for req := range p.requestChan {
go p.sendDNSRequest(req)
}
}()
}
func (p *DNSProxy) sendDNSRequest(req *dns.Msg) {
// 打开WiFi接口发送请求
handle, err := pcap.OpenLive(`\Device\NPF_{<Device-Hardw-Id>}`, 2048, false, 0)
if err != nil {
return
}
defer handle.Close()
// 构建DNS请求包
dnsLayer := &layers.DNS{
ID: req.Id,
QR: false,
OpCode: layers.DNSOpCodeQuery,
RD: true,
QDCount: 1,
Questions: []layers.DNSQuestion{
{
Name: []byte(req.Question[0].Name),
Type: layers.DNSTypeA,
Class: layers.DNSClassIN,
},
},
}
// 构建完整的数据包
buffer := gopacket.NewSerializeBuffer()
options := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
// 构建网络层
eth := &layers.Ethernet{
SrcMAC: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
EthernetType: layers.EthernetTypeIPv4,
}
ip := &layers.IPv4{
Version: 4,
TTL: 64,
SrcIP: net.IP{192, 168, 1, 100},
DstIP: net.IP{8, 8, 8, 8},
Protocol: layers.IPProtocolUDP,
}
udp := &layers.UDP{
SrcPort: 5353,
DstPort: 53,
}
udp.SetNetworkLayerForChecksum(ip)
gopacket.SerializeLayers(buffer, options,
eth,
ip,
udp,
dnsLayer,
)
handle.WritePacketData(buffer.Bytes())
}
func (p *DNSProxy) ListenToWifi() {
handle, err := pcap.OpenLive(`\Device\NPF_{<Device-Hardw-Id>}`, 2048, false, 100*time.Millisecond)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
dnsLayer := packet.Layer(layers.LayerTypeDNS)
if dnsLayer == nil {
continue
}
dns, ok := dnsLayer.(*layers.DNS)
if !ok || !dns.QR {
continue
}
// 将响应发送到通道
p.responseChan <- dns
}
}
func (p *DNSProxy) StartResponseProcessor() {
go func() {
for dnsResp := range p.responseChan {
p.requestsLock.RLock()
if req, exists := p.requests[dnsResp.ID]; exists {
select {
case req.Response <- dnsResp:
// 成功发送响应
default:
// 通道已满,丢弃响应
}
}
p.requestsLock.RUnlock()
}
}()
}
func (p *DNSProxy) convertToDNSMsg(dnsResp *layers.DNS) *dns.Msg {
msg := new(dns.Msg)
msg.Id = dnsResp.ID
msg.Response = true
// 转换问题部分
for _, q := range dnsResp.Questions {
msg.Question = append(msg.Question, dns.Question{
Name: string(q.Name),
Qtype: uint16(q.Type),
Qclass: uint16(q.Class),
})
}
// 转换答案部分
for _, a := range dnsResp.Answers {
rr := &dns.A{
Hdr: dns.RR_Header{
Name: string(a.Name),
Rrtype: uint16(a.Type),
Class: uint16(a.Class),
Ttl: a.TTL,
},
A: net.IP(a.IP),
}
msg.Answer = append(msg.Answer, rr)
}
return msg
}
func (p *DNSProxy) CleanupExpiredRequests() {
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
p.requestsLock.Lock()
now := time.Now()
for id, req := range p.requests {
if now.After(req.Timeout) {
close(req.Response)
delete(p.requests, id)
}
}
p.requestsLock.Unlock()
}
}()
}
func main() {
proxy := NewDNSProxy()
// 启动DNS服务器
server := &dns.Server{
Addr: ":53",
Net: "udp",
Handler: dns.HandlerFunc(proxy.handleRequest),
}
go server.ListenAndServe()
// 启动各个处理器
proxy.StartRequestProcessor()
proxy.StartResponseProcessor()
proxy.CleanupExpiredRequests()
// 监听WiFi接口
go proxy.ListenToWifi()
select {}
}
这个实现方案解决了你提到的问题:
- 并发请求处理:使用
sync.RWMutex保护请求映射,每个请求有独立的响应通道 - 超时处理:每个请求设置5秒超时,避免阻塞
- 异步处理:请求和响应通过通道异步传递,不会阻塞监听
- 内存管理:定期清理过期的请求,防止内存泄漏
关键改进点:
- 使用请求ID作为键的映射表来跟踪请求
- 每个请求创建独立的响应通道
- 异步处理请求和响应
- 添加超时机制和清理机制

