-
Notifications
You must be signed in to change notification settings - Fork 241
/
upstream.go
220 lines (189 loc) · 6.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
// Package upstream implements DNS clients for all known DNS encryption protocols
package upstream
import (
"crypto/x509"
"fmt"
"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"
)
// Upstream is an interface for a DNS resolver
type Upstream interface {
Exchange(m *dns.Msg) (*dns.Msg, error)
Address() string
}
// Options for AddressToUpstream func
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
// InsecureSkipVerify disables verifying the server's certificate.
InsecureSkipVerify bool
// VerifyServerCertificate used to be set to crypto/tls
// Config.VerifyPeerCertificate for DNS-over-HTTPS, DNS-over-QUIC,
// DNS-over-TLS.
VerifyServerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) 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
}
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.
//
// Early experiments MAY use port 8853. This port is marked in the IANA
// registry as unassigned. Note that prior to version -02 of this draft,
// experiments were directed to use port 784.
//
// See https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-02#section-10.2.1.
defaultPortDoQ = 8853
)
// 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;
// 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)
// TODO(e.burkov): Remove in the next major-minor release.
case "dns":
log.Info(
"warning: using %q scheme is deprecated and will be removed in future versions; "+
"use \"udp\" instead",
sch,
)
return newPlain(uu, opts.Timeout, false), nil
case "udp", "tcp":
return newPlain(uu, opts.Timeout, sch == "tcp"), nil
case "quic":
return newDoQ(uu, opts)
case "tls":
return newDoT(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 needed.
func addPort(u *url.URL, port int) {
if u != nil && u.Port() == "" {
u.Host = netutil.JoinHostPort(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.TypeToString[req.Question[0].Qtype]
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)
}