-
Notifications
You must be signed in to change notification settings - Fork 248
/
upstream.go
271 lines (231 loc) · 8.47 KB
/
upstream.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// Package upstream implements DNS clients for all known DNS encryption
// protocols.
package upstream
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/ameshkov/dnscrypt/v2"
"github.com/ameshkov/dnsstamps"
"github.com/miekg/dns"
"github.com/quic-go/quic-go/logging"
)
// Upstream is an interface for a DNS resolver.
type Upstream interface {
// Exchange sends the DNS query m to this upstream and returns the response
// that has been received or an error if something went wrong.
Exchange(m *dns.Msg) (*dns.Msg, error)
// Address returns the address of the upstream DNS resolver.
Address() string
// Closer used to close the upstreams properly. Exchange shouldn't be
// called after calling Close.
io.Closer
}
// Options for AddressToUpstream func. With these options we can configure the
// upstream properties.
type Options struct {
// Bootstrap is a list of DNS servers to be used to resolve
// DNS-over-HTTPS/DNS-over-TLS hostnames. Plain DNS, DNSCrypt, or
// DNS-over-HTTPS/DNS-over-TLS with IP addresses (not hostnames) could be
// used.
Bootstrap []string
// Timeout is the default upstream timeout. It's also used as a timeout for
// bootstrap DNS requests. Zero value disables the timeout.
Timeout time.Duration
// List of IP addresses of the upstream DNS server. If not empty, bootstrap
// DNS servers won't be used at all.
ServerIPAddrs []net.IP
// HTTPVersions is a list of HTTP versions that should be supported by the
// DNS-over-HTTPS client. If not set, HTTP/1.1 and HTTP/2 will be used.
HTTPVersions []HTTPVersion
// VerifyServerCertificate is used to set the VerifyPeerCertificate property
// of the *tls.Config for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS.
VerifyServerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
// VerifyConnection is used to set the VerifyConnection property
// of the *tls.Config for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS.
VerifyConnection func(state tls.ConnectionState) error
// VerifyDNSCryptCertificate is the callback the DNSCrypt server certificate
// will be passed to. It's called in dnsCrypt.exchangeDNSCrypt.
// Upstream.Exchange method returns any error caused by it.
VerifyDNSCryptCertificate func(cert *dnscrypt.Cert) error
// QUICTracer is an optional object that allows tracing every QUIC
// connection and logging every packet that goes through.
QUICTracer logging.Tracer
// InsecureSkipVerify disables verifying the server's certificate.
InsecureSkipVerify bool
// PreferIPv6 tells the bootstrapper to prefer IPv6 addresses for an
// upstream.
PreferIPv6 bool
}
// Clone copies o to a new struct. Note, that this is not a deep clone.
func (o *Options) Clone() (clone *Options) {
return &Options{
Bootstrap: o.Bootstrap,
Timeout: o.Timeout,
ServerIPAddrs: o.ServerIPAddrs,
HTTPVersions: o.HTTPVersions,
VerifyServerCertificate: o.VerifyServerCertificate,
VerifyConnection: o.VerifyConnection,
VerifyDNSCryptCertificate: o.VerifyDNSCryptCertificate,
InsecureSkipVerify: o.InsecureSkipVerify,
PreferIPv6: o.PreferIPv6,
}
}
// HTTPVersion is an enumeration of the HTTP versions that we support. Values
// that we use in this enumeration are also used as ALPN values.
type HTTPVersion string
const (
// HTTPVersion11 is HTTP/1.1.
HTTPVersion11 HTTPVersion = "http/1.1"
// HTTPVersion2 is HTTP/2.
HTTPVersion2 HTTPVersion = "h2"
// HTTPVersion3 is HTTP/3.
HTTPVersion3 HTTPVersion = "h3"
)
// DefaultHTTPVersions is the list of HTTPVersion that we use by default in
// the DNS-over-HTTPS client.
var DefaultHTTPVersions = []HTTPVersion{HTTPVersion11, HTTPVersion2}
const (
// defaultPortPlain is the default port for plain DNS.
defaultPortPlain = 53
// defaultPortDoH is the default port for DNS-over-HTTPS.
defaultPortDoH = 443
// defaultPortDoT is the default port for DNS-over-TLS.
defaultPortDoT = 853
// defaultPortDoQ is the default port for DNS-over-QUIC. Prior to version
// -10 of the draft experiments were directed to use ports 8853, 784.
//
// See https://www.rfc-editor.org/rfc/rfc9250.html#name-port-selection.
defaultPortDoQ = 853
)
// AddressToUpstream converts addr to an Upstream instance:
//
// - 8.8.8.8:53 or udp://dns.adguard.com for plain DNS;
// - tcp://8.8.8.8:53 for plain DNS-over-TCP;
// - tls://1.1.1.1 for DNS-over-TLS;
// - https://dns.adguard.com/dns-query for DNS-over-HTTPS;
// - h3://dns.google for DNS-over-HTTPS that only works with HTTP/3;
// - sdns://... for DNS stamp, see https://dnscrypt.info/stamps-specifications.
//
// opts are applied to the u. nil is a valid value for opts.
func AddressToUpstream(addr string, opts *Options) (u Upstream, err error) {
if opts == nil {
opts = &Options{}
}
if strings.Contains(addr, "://") {
var uu *url.URL
uu, err = url.Parse(addr)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", addr, err)
}
return urlToUpstream(uu, opts)
}
var host, port string
host, port, err = net.SplitHostPort(addr)
if err != nil {
return &plainDNS{address: net.JoinHostPort(addr, "53"), timeout: opts.Timeout}, nil
}
// Validate port.
portN, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return nil, fmt.Errorf("invalid address: %s", addr)
}
return &plainDNS{address: netutil.JoinHostPort(host, int(portN)), timeout: opts.Timeout}, nil
}
// urlToBoot creates a bootstrapper with the specified options.
func urlToBoot(u *url.URL, opts *Options) (b *bootstrapper, err error) {
if len(opts.ServerIPAddrs) == 0 {
return newBootstrapper(u, opts)
}
return newBootstrapperResolved(u, opts)
}
// urlToUpstream converts uu to an Upstream using opts.
func urlToUpstream(uu *url.URL, opts *Options) (u Upstream, err error) {
switch sch := uu.Scheme; sch {
case "sdns":
return stampToUpstream(uu, opts)
case "udp", "tcp":
return newPlain(uu, opts.Timeout, sch == "tcp"), nil
case "quic":
return newDoQ(uu, opts)
case "tls":
return newDoT(uu, opts)
case "h3":
opts.HTTPVersions = []HTTPVersion{HTTPVersion3}
uu.Scheme = "https"
return newDoH(uu, opts)
case "https":
return newDoH(uu, opts)
default:
return nil, fmt.Errorf("unsupported url scheme: %s", sch)
}
}
// stampToUpstream converts a DNS stamp to an Upstream
// options -- Upstream customization options
func stampToUpstream(upsURL *url.URL, opts *Options) (Upstream, error) {
stamp, err := dnsstamps.NewServerStampFromString(upsURL.String())
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", upsURL, err)
}
if stamp.ServerAddrStr != "" {
host, _, err := net.SplitHostPort(stamp.ServerAddrStr)
if err != nil {
host = stamp.ServerAddrStr
}
// Parse and add to options
ip := net.ParseIP(host)
if ip == nil {
return nil, fmt.Errorf("invalid server address in the stamp: %s", stamp.ServerAddrStr)
}
opts.ServerIPAddrs = []net.IP{ip}
}
switch stamp.Proto {
case dnsstamps.StampProtoTypePlain:
return &plainDNS{address: stamp.ServerAddrStr, timeout: opts.Timeout}, nil
case dnsstamps.StampProtoTypeDNSCrypt:
b, err := newBootstrapper(upsURL, opts)
if err != nil {
return nil, fmt.Errorf("bootstrap server parse: %s", err)
}
return &dnsCrypt{boot: b}, nil
case dnsstamps.StampProtoTypeDoH:
return AddressToUpstream(fmt.Sprintf("https://%s%s", stamp.ProviderName, stamp.Path), opts)
case dnsstamps.StampProtoTypeDoQ:
return AddressToUpstream(fmt.Sprintf("quic://%s%s", stamp.ProviderName, stamp.Path), opts)
case dnsstamps.StampProtoTypeTLS:
return AddressToUpstream(fmt.Sprintf("tls://%s", stamp.ProviderName), opts)
}
return nil, fmt.Errorf("unsupported protocol %v in %s", stamp.Proto, upsURL)
}
// addPort appends port to u if it's absent.
func addPort(u *url.URL, port int) {
if u != nil && u.Port() == "" {
u.Host = netutil.JoinHostPort(strings.Trim(u.Host, "[]"), port)
}
}
// Write to log DNS request information that we are going to send
func logBegin(upstreamAddress string, req *dns.Msg) {
qtype := ""
target := ""
if len(req.Question) != 0 {
qtype = dns.Type(req.Question[0].Qtype).String()
target = req.Question[0].Name
}
log.Debug("%s: sending request %s %s", upstreamAddress, qtype, target)
}
// Write to log about the result of DNS request
func logFinish(upstreamAddress string, err error) {
status := "ok"
if err != nil {
status = err.Error()
}
log.Debug("%s: response: %s", upstreamAddress, status)
}