-
Notifications
You must be signed in to change notification settings - Fork 62
/
dnsclient.go
173 lines (146 loc) · 4.26 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
161
162
163
164
165
166
167
168
169
170
171
172
173
package rdns
import (
"crypto/tls"
"net"
"strings"
"time"
"github.com/miekg/dns"
"github.com/sirupsen/logrus"
)
// DNSClient represents a simple DNS resolver for UDP or TCP.
type DNSClient struct {
id string
endpoint string
net string
pipeline *Pipeline // Pipeline also provides operation metrics.
opt DNSClientOptions
}
type Dialer interface {
Dial(net string, address string) (net.Conn, error)
}
type DNSClientOptions struct {
// Local IP to use for outbound connections. If nil, a local address is chosen.
LocalAddr net.IP
// Sets the EDNS0 UDP size for all queries sent upstream. If set to 0, queries
// are not changed.
UDPSize uint16
QueryTimeout time.Duration
// Optional dialer, e.g. proxy
Dialer Dialer
}
var _ Resolver = &DNSClient{}
// NewDNSClient returns a new instance of DNSClient which is a plain DNS resolver
// that supports pipelining over a single connection.
func NewDNSClient(id, endpoint, network string, opt DNSClientOptions) (*DNSClient, error) {
if err := validEndpoint(endpoint); err != nil {
return nil, err
}
client := GenericDNSClient{
Net: network,
Dialer: opt.Dialer,
TLSConfig: &tls.Config{},
LocalAddr: opt.LocalAddr,
Timeout: opt.QueryTimeout,
}
return &DNSClient{
id: id,
net: network,
endpoint: endpoint,
pipeline: NewPipeline(id, endpoint, client, opt.QueryTimeout),
opt: opt,
}, nil
}
// Resolve a DNS query.
func (d *DNSClient) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
// Packing a message is not always a read-only operation, make a copy
q = q.Copy()
logger(d.id, q, ci).WithFields(logrus.Fields{
"resolver": d.endpoint,
"protocol": d.net,
}).Debug("querying upstream resolver")
q = setUDPSize(q, d.opt.UDPSize)
// Remove padding before sending over the wire in plain
stripPadding(q)
return d.pipeline.Resolve(q)
}
func (d *DNSClient) String() string {
return d.id
}
// GenericDNSClient is a workaround for dns.Client not supporting custom dialers
// (only *net.Dialer) which prevents the use of proxies. It implements the same
// Dial functionality, while supporting custom dialers.
type GenericDNSClient struct {
Dialer Dialer
Net string
TLSConfig *tls.Config
LocalAddr net.IP
Timeout time.Duration
}
func (d GenericDNSClient) Dial(address string) (*dns.Conn, error) {
network := d.Net
// If we want TLS on it, perform the handshake
useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls")
network = strings.TrimSuffix(network, "-tls")
dialer := d.Dialer
if dialer == nil {
// Use a custom dialer if a local address was provided
if d.LocalAddr != nil {
switch network {
case "tcp":
dialer = &net.Dialer{LocalAddr: &net.TCPAddr{IP: d.LocalAddr}, Timeout: d.Timeout}
case "udp":
dialer = &net.Dialer{LocalAddr: &net.UDPAddr{IP: d.LocalAddr}, Timeout: d.Timeout}
}
} else {
dialer = &net.Dialer{}
}
}
var (
conn = &dns.Conn{
UDPSize: 4096,
}
err error
)
// Open a raw connection
conn.Conn, err = dialer.Dial(network, address)
if err != nil {
return nil, err
}
// Trick dns.Conn.ReadMsg() into thinking this is a packet connection (udp) so it
// correctly handles any length-prefixes
if network == "udp" {
conn.Conn = packetConnWrapper{conn.Conn}
}
if useTLS {
hostname, _, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
tlsConfig := d.TLSConfig
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
// Make sure a servername is set
if tlsConfig.ServerName == "" {
c := tlsConfig.Clone()
c.ServerName = hostname
tlsConfig = c
}
conn.Conn = tls.Client(conn.Conn, tlsConfig)
}
return conn, nil
}
// packetConnWrapper is another workaround for dns.Conn which checks if the Conn
// it has implements net.PacketConn and based on that distinguishes between a UDP
// connection (don't need length prefix) and TCP (need length prefix). This doesn't
// actually implement these, but dns.Conn.ReadMsg() doesn't use them either.
type packetConnWrapper struct {
net.Conn
}
var _ net.PacketConn = packetConnWrapper{}
func (c packetConnWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
panic("not implemented")
}
func (c packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) {
panic("not implemented")
}