-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdnsify.go
163 lines (140 loc) · 3.94 KB
/
dnsify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package dnsify
import (
"errors"
"math/rand"
"sync"
"time"
"github.com/miekg/dns"
)
const defaultPort = "53"
// Client is a DNS resolver client to resolve hostnames.
type Client struct {
resolvers []string
maxRetries int
rand *rand.Rand
mutex sync.RWMutex
}
// Result contains the results from a DNS resolution.
type Result struct {
IPs []string
TTL int
}
// New creates a new DNS client.
func New(baseResolvers []string, maxRetries int) *Client {
return &Client{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
maxRetries: maxRetries,
resolvers: baseResolvers,
}
}
// Resolve resolves a hostname and retrieves its A record IPs and TTL.
func (c *Client) Resolve(host string) (Result, error) {
msg := buildDNSMessage(host, dns.TypeA)
var result Result
for i := 0; i < c.maxRetries; i++ {
resolver := c.getRandomResolver()
answer, err := dns.Exchange(msg, resolver)
if err != nil || answer == nil || answer.Rcode != dns.RcodeSuccess {
continue
}
return parseARecords(answer), nil
}
return result, errors.New("failed to resolve after max retries")
}
// ResolveRaw resolves a hostname and retrieves raw DNS records of a specific type.
func (c *Client) ResolveRaw(host string, requestType uint16) ([]string, string, error) {
msg := buildDNSMessage(host, requestType)
for i := 0; i < c.maxRetries; i++ {
resolver := c.getRandomResolver()
answer, err := dns.Exchange(msg, resolver)
if err != nil || answer == nil || answer.Rcode != dns.RcodeSuccess {
continue
}
raw := answer.String()
return parseRecordsByType(answer, requestType), raw, nil
}
return nil, "", errors.New("failed to resolve after max retries")
}
// Do sends a DNS request and returns the raw DNS response.
func (c *Client) Do(msg *dns.Msg) (*dns.Msg, error) {
for i := 0; i < c.maxRetries; i++ {
resolver := c.getRandomResolver()
resp, err := dns.Exchange(msg, resolver)
if err == nil && resp != nil {
return resp, nil
}
}
return nil, errors.New("failed to send DNS request after max retries")
}
// getRandomResolver selects a random DNS resolver from the list.
func (c *Client) getRandomResolver() string {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.resolvers[c.rand.Intn(len(c.resolvers))]
}
// buildDNSMessage constructs a DNS message with the given host and request type.
func buildDNSMessage(host string, qtype uint16) *dns.Msg {
msg := new(dns.Msg)
msg.Id = dns.Id()
msg.RecursionDesired = true
msg.Question = []dns.Question{
{
Name: dns.Fqdn(host),
Qtype: qtype,
Qclass: dns.ClassINET,
},
}
return msg
}
// parseARecords extracts A records from the DNS response.
func parseARecords(answer *dns.Msg) Result {
var result Result
for _, record := range answer.Answer {
if a, ok := record.(*dns.A); ok {
result.IPs = append(result.IPs, a.A.String())
result.TTL = int(a.Header().Ttl)
}
}
return result
}
// parseRecordsByType extracts records of the requested type from the DNS response.
func parseRecordsByType(answer *dns.Msg, requestType uint16) []string {
var results []string
for _, record := range answer.Answer {
switch requestType {
case dns.TypeA:
if t, ok := record.(*dns.A); ok {
results = append(results, t.A.String())
}
case dns.TypeNS:
if t, ok := record.(*dns.NS); ok {
results = append(results, t.Ns)
}
case dns.TypeCNAME:
if t, ok := record.(*dns.CNAME); ok {
results = append(results, t.Target)
}
case dns.TypeSOA:
if t, ok := record.(*dns.SOA); ok {
results = append(results, t.String())
}
case dns.TypePTR:
if t, ok := record.(*dns.PTR); ok {
results = append(results, t.Ptr)
}
case dns.TypeMX:
if t, ok := record.(*dns.MX); ok {
results = append(results, t.String())
}
case dns.TypeTXT:
if t, ok := record.(*dns.TXT); ok {
results = append(results, t.String())
}
case dns.TypeAAAA:
if t, ok := record.(*dns.AAAA); ok {
results = append(results, t.AAAA.String())
}
}
}
return results
}