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
package resolver
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"time"
)
// ThroughServers are the DNS-over-HTTPS servers NewThrough asks, by IP
// address so no lookup is needed to reach them; their certificates name
// these addresses.
var ThroughServers = []string{"https://1.1.1.1/dns-query", "https://8.8.8.8/dns-query"}
// DialFunc opens a connection, such as a tunnel through Mozilla's proxy.
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
// throughClient builds the HTTP client of NewThrough; tests replace it to
// trust a local server.
var throughClient = func(dial DialFunc) *http.Client {
return &http.Client{
Timeout: queryTimeout,
Transport: &http.Transport{
Proxy: nil,
DialContext: dial,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: queryTimeout,
MaxIdleConns: 2,
// An idle connection is a tunnel that keeps the proxy in use;
// close it well before the proxy would sleep.
IdleConnTimeout: time.Minute,
},
}
}
// NewThrough returns a resolver that asks ThroughServers over connections
// from dial. It never falls back to the system resolver: on a filtered
// network that resolver answers blocked names with the address of a block
// page. Answers are cached like those of New.
func NewThrough(dial DialFunc) *Resolver {
client := throughClient(dial)
r := &Resolver{cache: make(map[string]cacheEntry)}
for _, u := range ThroughServers {
r.servers = append(r.servers, &dohServer{url: u, client: client})
}
return r
}
// Exchange sends a whole DNS query in wire format to every DNS-over-HTTPS
// server at once and returns the first answer as it is, for DNS a client
// sends through the proxy (Mozilla does not connect to port 53). Servers
// of other kinds are not asked.
func (r *Resolver) Exchange(ctx context.Context, query []byte) ([]byte, error) {
var servers []*dohServer
if r != nil {
for _, s := range r.servers {
if d, ok := s.(*dohServer); ok {
servers = append(servers, d)
}
}
}
if len(servers) == 0 {
return nil, errors.New("no DNS-over-HTTPS server to ask")
}
ctx, cancel := context.WithTimeout(ctx, queryTimeout)
defer cancel()
type answer struct {
msg []byte
source string
err error
}
answers := make(chan answer, len(servers))
for _, s := range servers {
go func() {
msg, err := s.exchange(ctx, query)
answers <- answer{msg, s.String(), err}
}()
}
var errs serverErrors
for range servers {
a := <-answers
if a.err == nil {
return a.msg, nil
}
errs = append(errs, fmt.Errorf("%s: %w", a.source, a.err))
}
return nil, fmt.Errorf("asking DNS over HTTPS failed: %w", errs)
}