/
nameserver.go
289 lines (247 loc) Β· 9.03 KB
/
nameserver.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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package nameserver
import (
"context"
"errors"
"net"
"strings"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portbase/modules/subsystems"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/firewall"
"github.com/safing/portmaster/nameserver/nsutil"
"github.com/safing/portmaster/netenv"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/resolver"
"github.com/miekg/dns"
)
var (
module *modules.Module
dnsServer *dns.Server
defaultNameserverAddress = "0.0.0.0:53"
)
func init() {
module = modules.Register("nameserver", prep, start, stop, "core", "resolver")
subsystems.Register(
"dns",
"Secure DNS",
"DNS resolver with scoping and DNS-over-TLS",
module,
"config:dns/",
nil,
)
}
func prep() error {
return registerConfig()
}
func start() error {
logFlagOverrides()
dnsServer = &dns.Server{Addr: nameserverAddressConfig(), Net: "udp"}
dns.HandleFunc(".", handleRequestAsWorker)
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
err := dnsServer.ListenAndServe()
if err != nil {
// check if we are shutting down
if module.IsStopping() {
return nil
}
// is something blocking our port?
checkErr := checkForConflictingService()
if checkErr != nil {
return checkErr
}
}
return err
})
return nil
}
func stop() error {
if dnsServer != nil {
return dnsServer.Shutdown()
}
return nil
}
func handleRequestAsWorker(w dns.ResponseWriter, query *dns.Msg) {
err := module.RunWorker("dns request", func(ctx context.Context) error {
return handleRequest(ctx, w, query)
})
if err != nil {
log.Warningf("nameserver: failed to handle dns request: %s", err)
}
}
func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) error { //nolint:gocognit // TODO
// Only process first question, that's how everyone does it.
if len(request.Question) == 0 {
return errors.New("missing question")
}
originalQuestion := request.Question[0]
// Check if we are handling a non-standard query name.
var nonStandardQuestionFormat bool
lowerCaseQuestion := strings.ToLower(originalQuestion.Name)
if lowerCaseQuestion != originalQuestion.Name {
nonStandardQuestionFormat = true
}
// Create query for the resolver.
q := &resolver.Query{
FQDN: lowerCaseQuestion,
QType: dns.Type(originalQuestion.Qtype),
}
// Get remote address of request.
remoteAddr, ok := w.RemoteAddr().(*net.UDPAddr)
if !ok {
log.Warningf("nameserver: failed to get remote address of request for %s%s, ignoring", q.FQDN, q.QType)
return nil
}
// Start context tracer for context-aware logging.
ctx, tracer := log.AddTracer(ctx)
defer tracer.Submit()
tracer.Tracef("nameserver: handling new request for %s from %s:%d", q.ID(), remoteAddr.IP, remoteAddr.Port)
// Check if there are more than one question.
if len(request.Question) > 1 {
tracer.Warningf("nameserver: received more than one question from (%s:%d), first question is %s", remoteAddr.IP, remoteAddr.Port, q.ID())
}
// Setup quick reply function.
reply := func(responder nsutil.Responder, rrProviders ...nsutil.RRProvider) error {
err := sendResponse(ctx, w, request, responder, rrProviders...)
// Log error here instead of returning it in order to keep the context.
if err != nil {
tracer.Errorf("nameserver: %s", err)
}
return nil
}
// Return with server failure if offline.
if netenv.GetOnlineStatus() == netenv.StatusOffline &&
!netenv.IsConnectivityDomain(q.FQDN) {
tracer.Debugf("nameserver: not resolving %s, device is offline", q.FQDN)
return reply(nsutil.ServerFailure("resolving disabled, device is offline"))
}
// Check the Query Class.
if originalQuestion.Qclass != dns.ClassINET {
// we only serve IN records, return nxdomain
tracer.Warningf("nameserver: only IN record requests are supported but received QClass %d, returning NXDOMAIN", originalQuestion.Qclass)
return reply(nsutil.Refused("unsupported qclass"))
}
// Handle request for localhost.
if strings.HasSuffix(q.FQDN, "localhost.") {
tracer.Tracef("nameserver: returning localhost records")
return reply(nsutil.Localhost())
}
// Authenticate request - only requests from the local host, but with any of its IPs, are allowed.
local, err := netenv.IsMyIP(remoteAddr.IP)
if err != nil {
tracer.Warningf("nameserver: failed to check if request for %s%s is local: %s", q.FQDN, q.QType, err)
return nil // Do no reply, drop request immediately.
}
if !local {
tracer.Warningf("nameserver: external request for %s%s, ignoring", q.FQDN, q.QType)
return nil // Do no reply, drop request immediately.
}
// Validate domain name.
if !netutils.IsValidFqdn(q.FQDN) {
tracer.Debugf("nameserver: domain name %s is invalid, refusing", q.FQDN)
return reply(nsutil.Refused("invalid domain"))
}
// Get connection for this request. This identifies the process behind the request.
conn := network.NewConnectionFromDNSRequest(ctx, q.FQDN, nil, packet.IPv4, remoteAddr.IP, uint16(remoteAddr.Port))
conn.Lock()
defer conn.Unlock()
// Once we decided on the connection we might need to save it to the database,
// so we defer that check for now.
defer func() {
switch conn.Verdict {
// We immediately save blocked, dropped or failed verdicts so
// they pop up in the UI.
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
conn.Save()
// For undecided or accepted connections we don't save them yet, because
// that will happen later anyway.
case network.VerdictUndecided, network.VerdictAccept,
network.VerdictRerouteToNameserver, network.VerdictRerouteToTunnel:
return
default:
tracer.Warningf("nameserver: unexpected verdict %s for connection %s, not saving", conn.Verdict, conn)
}
}()
// Check request with the privacy filter before resolving.
firewall.DecideOnConnection(ctx, conn, nil)
// Check if there is a responder from the firewall.
// In special cases, the firewall might want to respond the query itself.
// A reason for this might be that the request is sink-holed to a forced
// IP address in which case we "accept" it, but let the firewall handle
// the resolving as it wishes.
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
// Save the request as open, as we don't know if there will be a connection or not.
network.SaveOpenDNSRequest(conn)
tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg)
return reply(responder)
}
// Check if there is Verdict to act upon.
switch conn.Verdict {
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
tracer.Infof("nameserver: request for %s from %s %s", q.ID(), conn.Process(), conn.Verdict.Verb())
return reply(conn, conn)
}
// Save security level to query, so that the resolver can react to configuration.
q.SecurityLevel = conn.Process().Profile().SecurityLevel()
// Resolve request.
rrCache, err := resolver.Resolve(ctx, q)
if err != nil {
// React to special errors.
switch {
case errors.Is(err, resolver.ErrNotFound):
tracer.Tracef("nameserver: %s", err)
return reply(nsutil.NxDomain("nxdomain: " + err.Error()))
case errors.Is(err, resolver.ErrBlocked):
tracer.Tracef("nameserver: %s", err)
return reply(nsutil.ZeroIP("blocked: " + err.Error()))
case errors.Is(err, resolver.ErrLocalhost):
tracer.Tracef("nameserver: returning localhost records")
return reply(nsutil.Localhost())
default:
tracer.Warningf("nameserver: failed to resolve %s: %s", q.ID(), err)
return reply(nsutil.ServerFailure("internal error: " + err.Error()))
}
}
if rrCache == nil {
tracer.Warning("nameserver: received successful, but empty reply from resolver")
return reply(nsutil.ServerFailure("internal error: empty reply"))
}
tracer.Trace("nameserver: deciding on resolved dns")
rrCache = firewall.DecideOnResolvedDNS(ctx, conn, q, rrCache)
if rrCache == nil {
// Check again if there is a responder from the firewall.
if responder, ok := conn.Reason.Context.(nsutil.Responder); ok {
// Save the request as open, as we don't know if there will be a connection or not.
network.SaveOpenDNSRequest(conn)
tracer.Infof("nameserver: handing over request for %s to filter responder: %s", q.ID(), conn.Reason.Msg)
return reply(responder)
}
// Request was blocked by the firewall.
switch conn.Verdict {
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
tracer.Infof(
"nameserver: returning %s response for %s to %s",
conn.Verdict.Verb(),
q.ID(),
conn.Process(),
)
return reply(conn, conn)
}
}
// Save dns request as open.
defer network.SaveOpenDNSRequest(conn)
// Revert back to non-standard question format, if we had to convert.
if nonStandardQuestionFormat {
rrCache.ReplaceAnswerNames(originalQuestion.Name)
}
// Reply with successful response.
tracer.Infof(
"nameserver: returning %s response (%s) for %s to %s",
conn.Verdict.Verb(),
dns.RcodeToString[rrCache.RCode],
q.ID(),
conn.Process(),
)
return reply(rrCache, conn, rrCache)
}