-
-
Notifications
You must be signed in to change notification settings - Fork 274
/
resolver-https.go
170 lines (142 loc) Β· 3.84 KB
/
resolver-https.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
package resolver
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"sync"
"time"
"github.com/miekg/dns"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/netenv"
)
// HTTPSResolver is a resolver using just a single tcp connection with pipelining.
type HTTPSResolver struct {
BasicResolverConn
client *http.Client
clientLock sync.RWMutex
}
// HTTPSQuery holds the query information for a hTTPSResolverConn.
type HTTPSQuery struct {
Query *Query
Response chan *dns.Msg
}
// MakeCacheRecord creates an RRCache record from a reply.
func (tq *HTTPSQuery) MakeCacheRecord(reply *dns.Msg, resolverInfo *ResolverInfo) *RRCache {
return &RRCache{
Domain: tq.Query.FQDN,
Question: tq.Query.QType,
RCode: reply.Rcode,
Answer: reply.Answer,
Ns: reply.Ns,
Extra: reply.Extra,
Resolver: resolverInfo.Copy(),
}
}
// NewHTTPSResolver returns a new HTTPSResolver.
func NewHTTPSResolver(resolver *Resolver) *HTTPSResolver {
newResolver := &HTTPSResolver{
BasicResolverConn: BasicResolverConn{
resolver: resolver,
},
}
newResolver.BasicResolverConn.init()
newResolver.refreshClient()
return newResolver
}
// Query executes the given query against the resolver.
func (hr *HTTPSResolver) Query(ctx context.Context, q *Query) (*RRCache, error) {
queryStarted := time.Now()
dnsQuery := new(dns.Msg)
dnsQuery.SetQuestion(q.FQDN, uint16(q.QType))
// Pack query and convert to base64 string
buf, err := dnsQuery.Pack()
if err != nil {
return nil, err
}
b64dns := base64.RawURLEncoding.EncodeToString(buf)
// Build and execute http request
url := &url.URL{
Scheme: "https",
Host: hr.resolver.ServerAddress,
Path: hr.resolver.Path,
ForceQuery: true,
RawQuery: fmt.Sprintf("dns=%s", b64dns),
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
if err != nil {
return nil, err
}
// Lock client for usage.
hr.clientLock.RLock()
defer hr.clientLock.RUnlock()
// TODO: Check age of client and force a refresh similar to the TCP resolver.
resp, err := hr.client.Do(request)
if err != nil {
// Hint network environment at failed connection.
netenv.ReportFailedConnection()
return nil, err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http request failed with %s", resp.Status)
}
// Try to read the result
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
reply := new(dns.Msg)
err = reply.Unpack(body)
if err != nil {
return nil, err
}
// Hint network environment at successful connection.
netenv.ReportSuccessfulConnection()
// Report request duration for metrics.
reportRequestDuration(queryStarted, hr.resolver)
newRecord := &RRCache{
Domain: q.FQDN,
Question: q.QType,
RCode: reply.Rcode,
Answer: reply.Answer,
Ns: reply.Ns,
Extra: reply.Extra,
Resolver: hr.resolver.Info.Copy(),
}
// TODO: check if reply.Answer is valid
return newRecord, nil
}
// ForceReconnect forces the resolver to re-establish the connection to the server.
func (hr *HTTPSResolver) ForceReconnect(ctx context.Context) {
hr.refreshClient()
log.Tracer(ctx).Tracef("resolver: created new HTTP client for %s", hr.resolver)
}
func (hr *HTTPSResolver) refreshClient() {
// Lock client for changing.
hr.clientLock.Lock()
defer hr.clientLock.Unlock()
// Attempt to close connection of previous client.
if hr.client != nil {
hr.client.CloseIdleConnections()
}
// Create new client.
tr := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: hr.resolver.Info.Domain,
// TODO: use portbase rng
},
IdleConnTimeout: 1 * time.Minute,
TLSHandshakeTimeout: defaultConnectTimeout,
}
hr.client = &http.Client{
Transport: tr,
Timeout: maxRequestTimeout,
}
}