-
Notifications
You must be signed in to change notification settings - Fork 1
/
dnsclient.go
160 lines (141 loc) · 3.62 KB
/
dnsclient.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
package netem
//
// DNS client code
//
import (
"context"
"errors"
"net"
"github.com/miekg/dns"
)
// DNSRoundTrip performs a DNS round trip using a given [UnderlyingNetwork].
func DNSRoundTrip(
ctx context.Context,
stack UnderlyingNetwork,
ipAddress string,
query *dns.Msg,
) (*dns.Msg, error) {
responsech := make(chan *dns.Msg, 1)
errch := make(chan error, 1)
go dnsRoundTripAsync(ctx, stack, ipAddress, query, responsech, errch)
select {
case resp := <-responsech:
return resp, nil
case err := <-errch:
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
// dnsRoundTripAsync is an async DNS round trip.
func dnsRoundTripAsync(
ctx context.Context,
stack UnderlyingNetwork,
ipAddress string,
query *dns.Msg,
responsech chan<- *dns.Msg,
errch chan<- error,
) {
response, err := dnsRoundTrip(ctx, stack, ipAddress, query)
if err != nil {
errch <- err
return
}
responsech <- response
}
// dnsRoundTrip performs a DNS round trip using a given [UnderlyingNetwork].
func dnsRoundTrip(
ctx context.Context,
stack UnderlyingNetwork,
ipAddress string,
query *dns.Msg,
) (*dns.Msg, error) {
// create an UDP network connection
addrport := net.JoinHostPort(ipAddress, "53")
conn, err := stack.DialContext(ctx, "udp", addrport)
if err != nil {
return nil, err
}
if deadline, good := ctx.Deadline(); good {
_ = conn.SetDeadline(deadline)
}
defer conn.Close()
// serialize the DNS query
rawQuery, err := query.Pack()
if err != nil {
return nil, err
}
// send the query
if _, err := conn.Write(rawQuery); err != nil {
return nil, err
}
// receive the response from the DNS server
buffer := make([]byte, 8000)
count, err := conn.Read(buffer)
if err != nil {
return nil, err
}
rawResponse := buffer[:count]
// unmarshal the response
response := &dns.Msg{}
if err := response.Unpack(rawResponse); err != nil {
return nil, err
}
return response, nil
}
// ErrDNSNoAnswer is returned when the server response does not contain any
// answer for the original query (i.e., no IPv4 addresses).
var ErrDNSNoAnswer = errors.New("netem: dns: no answer from DNS server")
// ErrDNSNoSuchHost is returned in case of NXDOMAIN.
var ErrDNSNoSuchHost = errors.New("netem: dns: no such host")
// ErrDNSServerMisbehaving is the error we return for cases different from NXDOMAIN.
var ErrDNSServerMisbehaving = errors.New("netem: dns: server misbehaving")
// DNSParseResponse parses a [dns.Msg] into a getaddrinfo response
func DNSParseResponse(query, resp *dns.Msg) ([]string, string, error) {
// make sure resp is a response and relates to the original query ID
if !resp.Response {
return nil, "", ErrDNSServerMisbehaving
}
if resp.Id != query.Id {
return nil, "", ErrDNSServerMisbehaving
}
// attempt to map errors like the Go standard library would do
switch resp.Rcode {
case dns.RcodeSuccess:
// continue processing the response
case dns.RcodeNameError:
return nil, "", ErrDNSNoSuchHost
default:
return nil, "", ErrDNSServerMisbehaving
}
// search for A answers and CNAME
var (
A []string
CNAME string
)
for _, answer := range resp.Answer {
switch v := answer.(type) {
case *dns.A:
A = append(A, v.A.String())
case *dns.CNAME:
CNAME = v.Target
}
}
// make sure we emit the same error the Go stdlib emits
if len(A) <= 0 {
return nil, "", ErrDNSNoAnswer
}
return A, CNAME, nil
}
// NewDNSRequestA creates a new A request.
func NewDNSRequestA(domain string) *dns.Msg {
query := &dns.Msg{}
query.RecursionDesired = true
query.Id = dns.Id()
query.Question = []dns.Question{{
Name: dns.CanonicalName(domain),
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}}
return query
}