forked from ooni/probe-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdnstransport.go
165 lines (157 loc) · 5.65 KB
/
dnstransport.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
package netx
//
// DNSTransport from Config.
//
// TODO(bassosimone): this code should be refactored to return
// a DNSTransport rather than a model.Resolver. With this in mind,
// I've named this file dnstransport.go.
// TODO(https://github.com/ooni/probe/issues/2121#issuecomment-1147424810)
//
import (
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)
// NewDNSClient creates a new DNS client. The config argument is used to
// create the underlying Dialer and/or HTTP transport, if needed. The URL
// argument describes the kind of client that we want to make:
//
// - if the URL is `doh://google` or `doh://cloudflare` or the URL
// starts with `https://`, then we create a DoH client.
//
// - if the URL is `` or `system:///`, then we create a system client,
// i.e. a client using the system resolver.
//
// - if the URL starts with `udp://`, then we create a client using
// a resolver that uses the specified UDP endpoint.
//
// We return error if the URL does not parse or the URL scheme does not
// fall into one of the cases described above.
//
// If config.ResolveSaver is not nil and we're creating an underlying
// resolver where this is possible, we will also save events.
func NewDNSClient(config Config, URL string) (model.Resolver, error) {
return NewDNSClientWithOverrides(config, URL, "", "", "")
}
// NewDNSClientWithOverrides creates a new DNS client, similar to NewDNSClient,
// with the option to override the default Hostname and SNI.
func NewDNSClientWithOverrides(config Config, URL, hostOverride, SNIOverride,
TLSVersion string) (model.Resolver, error) {
// We should split this function in smaller and testable units
// TODO(https://github.com/ooni/probe/issues/2121#issuecomment-1147424810)
switch URL {
case "doh://google":
URL = "https://dns.google/dns-query"
case "doh://cloudflare":
URL = "https://cloudflare-dns.com/dns-query"
case "":
URL = "system:///"
}
resolverURL, err := url.Parse(URL)
if err != nil {
return nil, err
}
config.TLSConfig = &tls.Config{ServerName: SNIOverride}
if err := netxlite.ConfigureTLSVersion(config.TLSConfig, TLSVersion); err != nil {
return nil, err
}
switch resolverURL.Scheme {
case "system":
return netxlite.NewUnwrappedStdlibResolver(), nil
case "https":
config.TLSConfig.NextProtos = []string{"h2", "http/1.1"}
httpClient := &http.Client{Transport: NewHTTPTransport(config)}
var txp model.DNSTransport = netxlite.NewUnwrappedDNSOverHTTPSTransportWithHostOverride(
httpClient, URL, hostOverride)
txp = config.Saver.WrapDNSTransport(txp) // safe when config.Saver == nil
return netxlite.NewUnwrappedSerialResolver(txp), nil
case "udp":
dialer := NewDialer(config)
endpoint, err := makeValidEndpoint(resolverURL)
if err != nil {
return nil, err
}
var txp model.DNSTransport = netxlite.NewUnwrappedDNSOverUDPTransport(
dialer, endpoint)
txp = config.Saver.WrapDNSTransport(txp) // safe when config.Saver == nil
return netxlite.NewUnwrappedSerialResolver(txp), nil
case "dot":
config.TLSConfig.NextProtos = []string{"dot"}
tlsDialer := NewTLSDialer(config)
endpoint, err := makeValidEndpoint(resolverURL)
if err != nil {
return nil, err
}
var txp model.DNSTransport = netxlite.NewUnwrappedDNSOverTLSTransport(
tlsDialer.DialTLSContext, endpoint)
txp = config.Saver.WrapDNSTransport(txp) // safe when config.Saver == nil
return netxlite.NewUnwrappedSerialResolver(txp), nil
case "tcp":
dialer := NewDialer(config)
endpoint, err := makeValidEndpoint(resolverURL)
if err != nil {
return nil, err
}
var txp model.DNSTransport = netxlite.NewUnwrappedDNSOverTCPTransport(
dialer.DialContext, endpoint)
txp = config.Saver.WrapDNSTransport(txp) // safe when config.Saver == nil
return netxlite.NewUnwrappedSerialResolver(txp), nil
default:
return nil, errors.New("unsupported resolver scheme")
}
}
// makeValidEndpoint makes a valid endpoint for DoT and Do53 given the
// input URL representing such endpoint. Specifically, we are
// concerned with the case where the port is missing. In such a
// case, we ensure that we are using the default port 853 for DoT
// and default port 53 for TCP and UDP.
func makeValidEndpoint(URL *url.URL) (string, error) {
// Implementation note: when we're using a quoted IPv6
// address, URL.Host contains the quotes but instead the
// return value from URL.Hostname() does not.
//
// For example:
//
// - Host: [2620:fe::9]
// - Hostname(): 2620:fe::9
//
// We need to keep this in mind when trying to determine
// whether there is also a port or not.
//
// So the first step is to check whether URL.Host is already
// a whatever valid TCP/UDP endpoint and, if so, use it.
if _, _, err := net.SplitHostPort(URL.Host); err == nil {
return URL.Host, nil
}
// Here we should add a test case for when the host is empty
// TODO(https://github.com/ooni/probe/issues/2121#issuecomment-1147424810)
// The second step is to assume that appending the default port
// to a host parsed by url.Parse should be giving us a valid
// endpoint. The possibilities in fact are:
//
// 1. domain w/o port
// 2. IPv4 w/o port
// 3. square bracket quoted IPv6 w/o port
// 4. other
//
// In the first three cases, appending a port leads us to a
// good endpoint. The fourth case does not.
//
// For this reason we check again whether we can split it using
// net.SplitHostPort. If we cannot, we were in case four.
host := URL.Host
if URL.Scheme == "dot" {
host += ":853"
} else {
host += ":53"
}
if _, _, err := net.SplitHostPort(host); err != nil {
return "", err
}
// Otherwise it's one of the three valid cases above.
return host, nil
}