/
resolvers.go
615 lines (529 loc) Β· 18 KB
/
resolvers.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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
package resolver
import (
"context"
"fmt"
"net"
"net/url"
"sort"
"strconv"
"strings"
"sync"
"github.com/miekg/dns"
"golang.org/x/net/publicsuffix"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portmaster/service/netenv"
"github.com/safing/portmaster/service/network/netutils"
)
const maxSearchDomains = 100
// Scope defines a domain scope and which resolvers can resolve it.
type Scope struct {
Domain string
Resolvers []*Resolver
}
const (
parameterName = "name"
parameterVerify = "verify"
parameterIP = "ip"
parameterBlockedIf = "blockedif"
parameterSearch = "search"
parameterSearchOnly = "search-only"
)
var (
globalResolvers []*Resolver // all (global) resolvers
localResolvers []*Resolver // all resolvers that are in site-local or link-local IP ranges
systemResolvers []*Resolver // all resolvers that were assigned by the system
localScopes []*Scope // list of scopes with a list of local resolvers that can resolve the scope
activeResolvers map[string]*Resolver // lookup map of all resolvers
currentResolverConfig []string // current active resolver config, to detect changes
resolverInitDomains map[string]struct{} // a set with all domains of the dns resolvers
resolversLock sync.RWMutex
)
func indexOfScope(domain string, list []*Scope) int {
for k, v := range list {
if v.Domain == domain {
return k
}
}
return -1
}
func getActiveResolverByIDWithLocking(server string) *Resolver {
resolversLock.RLock()
defer resolversLock.RUnlock()
resolver, ok := activeResolvers[server]
if ok {
return resolver
}
return nil
}
func formatIPAndPort(ip net.IP, port uint16) string {
var address string
if ipv4 := ip.To4(); ipv4 != nil {
address = fmt.Sprintf("%s:%d", ipv4.String(), port)
} else {
address = fmt.Sprintf("[%s]:%d", ip.String(), port)
}
return address
}
func resolverConnFactory(resolver *Resolver) ResolverConn {
switch resolver.Info.Type {
case ServerTypeTCP:
return NewTCPResolver(resolver)
case ServerTypeDoT:
return NewTCPResolver(resolver).UseTLS()
case ServerTypeDoH:
return NewHTTPSResolver(resolver)
case ServerTypeDNS:
return NewPlainResolver(resolver)
default:
return nil
}
}
func createResolver(resolverURL, source string) (*Resolver, bool, error) {
u, err := url.Parse(resolverURL)
if err != nil {
return nil, false, err
}
if resolverInitDomains == nil {
resolverInitDomains = make(map[string]struct{})
}
switch u.Scheme {
case ServerTypeDNS, ServerTypeDoT, ServerTypeDoH, ServerTypeTCP:
case HTTPSProtocol:
u.Scheme = ServerTypeDoH
case TLSProtocol:
u.Scheme = ServerTypeDoT
default:
return nil, false, fmt.Errorf("DNS resolver scheme %q invalid", u.Scheme)
}
query := u.Query()
// Create Resolver object
newResolver := &Resolver{
ConfigURL: resolverURL,
Info: &ResolverInfo{
Name: query.Get(parameterName),
Type: u.Scheme,
Source: source,
IP: nil,
Domain: "",
IPScope: netutils.Global,
Port: 0,
},
ServerAddress: "",
Path: u.Path, // Used for DoH
UpstreamBlockDetection: "",
}
// Get parameters and check if keys exist.
err = checkAndSetResolverParamters(u, newResolver)
if err != nil {
return nil, false, err
}
// Check block detection type.
newResolver.UpstreamBlockDetection = query.Get(parameterBlockedIf)
if newResolver.UpstreamBlockDetection == "" {
newResolver.UpstreamBlockDetection = BlockDetectionZeroIP
}
switch newResolver.UpstreamBlockDetection {
case BlockDetectionDisabled, BlockDetectionEmptyAnswer, BlockDetectionRefused, BlockDetectionZeroIP:
default:
return nil, false, fmt.Errorf("invalid value for upstream block detection (blockedif=)")
}
// Get ip scope if we have ip
if newResolver.Info.IP != nil {
newResolver.Info.IPScope = netutils.GetIPScope(newResolver.Info.IP)
// Skip localhost resolvers from the OS, but not if configured.
if newResolver.Info.IPScope.IsLocalhost() && source == ServerSourceOperatingSystem {
return nil, true, nil // skip
}
}
// Parse search domains.
searchDomains := query.Get(parameterSearch)
if searchDomains != "" {
err = configureSearchDomains(newResolver, strings.Split(searchDomains, ","), true)
if err != nil {
return nil, false, err
}
}
// Check if searchOnly is set and valid.
if query.Has(parameterSearchOnly) {
newResolver.SearchOnly = true
if query.Get(parameterSearchOnly) != "" {
return nil, false, fmt.Errorf("%s may only be used as an empty parameter", parameterSearchOnly)
}
if len(newResolver.Search) == 0 {
return nil, false, fmt.Errorf("cannot use %s without search scopes", parameterSearchOnly)
}
}
newResolver.Conn = resolverConnFactory(newResolver)
return newResolver, false, nil
}
func checkAndSetResolverParamters(u *url.URL, resolver *Resolver) error {
// Check if we are using domain name and if it's in a valid scheme
ip := net.ParseIP(u.Hostname())
hostnameIsDomaion := (ip == nil)
if ip == nil && u.Scheme != ServerTypeDoH && u.Scheme != ServerTypeDoT {
return fmt.Errorf("resolver IP %q is invalid", u.Hostname())
}
// Add default port for scheme if it is missing.
port, err := parsePortFromURL(u)
if err != nil {
return err
}
resolver.Info.Port = port
resolver.Info.IP = ip
query := u.Query()
for key := range query {
switch key {
case parameterName,
parameterVerify,
parameterIP,
parameterBlockedIf,
parameterSearch,
parameterSearchOnly:
// Known key, continue.
default:
// Unknown key, abort.
return fmt.Errorf(`unknown parameter "%q"`, key)
}
}
resolver.Info.Domain = query.Get(parameterVerify)
paramterServerIP := query.Get(parameterIP)
if u.Scheme == ServerTypeDoT || u.Scheme == ServerTypeDoH {
// Check if IP and Domain are set correctly
switch {
case hostnameIsDomaion && resolver.Info.Domain != "":
return fmt.Errorf("cannot set the domain name via both the hostname in the URL and the verify parameter")
case !hostnameIsDomaion && resolver.Info.Domain == "":
return fmt.Errorf("verify parameter must be set when using ip as domain")
case !hostnameIsDomaion && paramterServerIP != "":
return fmt.Errorf("cannot set the IP address via both the hostname in the URL and the ip parameter")
}
// Parse and set IP and Domain to the resolver
switch {
case hostnameIsDomaion && paramterServerIP != "": // domain and ip as parameter
resolver.Info.IP = net.ParseIP(paramterServerIP)
resolver.ServerAddress = net.JoinHostPort(paramterServerIP, strconv.Itoa(int(resolver.Info.Port)))
resolver.Info.Domain = u.Hostname()
case !hostnameIsDomaion && resolver.Info.Domain != "": // ip and domain as parameter
resolver.ServerAddress = net.JoinHostPort(ip.String(), strconv.Itoa(int(resolver.Info.Port)))
case hostnameIsDomaion && resolver.Info.Domain == "" && paramterServerIP == "": // only domain
resolver.Info.Domain = u.Hostname()
resolver.ServerAddress = net.JoinHostPort(resolver.Info.Domain, strconv.Itoa(int(port)))
}
if ip == nil {
resolverInitDomains[dns.Fqdn(resolver.Info.Domain)] = struct{}{}
}
} else {
if resolver.Info.Domain != "" {
return fmt.Errorf("domain verification is only supported by DoT and DoH servers")
}
resolver.ServerAddress = net.JoinHostPort(ip.String(), strconv.Itoa(int(resolver.Info.Port)))
}
return nil
}
func parsePortFromURL(url *url.URL) (uint16, error) {
var port uint16
hostPort := url.Port()
if hostPort != "" {
// There is a port in the url
parsedPort, err := strconv.ParseUint(hostPort, 10, 16)
if err != nil {
return 0, fmt.Errorf("invalid port %q", url.Port())
}
port = uint16(parsedPort)
} else {
// set the default port for the protocol
switch {
case url.Scheme == ServerTypeDNS, url.Scheme == ServerTypeTCP:
port = 53
case url.Scheme == ServerTypeDoH:
port = 443
case url.Scheme == ServerTypeDoT:
port = 853
default:
return 0, fmt.Errorf("cannot determine port for %q", url.Scheme)
}
}
return port, nil
}
func configureSearchDomains(resolver *Resolver, searches []string, hardfail bool) error {
resolver.Search = make([]string, 0, len(searches))
// Check all search domains.
for i, value := range searches {
trimmedDomain := strings.ToLower(strings.Trim(value, "."))
err := checkSearchScope(trimmedDomain)
if err != nil {
if hardfail {
resolver.Search = nil
return fmt.Errorf("failed to validate search domain #%d: %w", i+1, err)
}
log.Warningf("resolver: skipping invalid search domain for resolver %s: %s", resolver, utils.SafeFirst16Chars(value))
} else {
resolver.Search = append(resolver.Search, fmt.Sprintf(".%s.", trimmedDomain))
}
}
// Limit search domains to mitigate exploitation via malicious local resolver.
if len(resolver.Search) > maxSearchDomains {
if hardfail {
return fmt.Errorf("too many search domains, for security reasons only %d search domains are supported", maxSearchDomains)
}
log.Warningf("resolver: limiting search domains for resolver %s to %d for security reasons", resolver, maxSearchDomains)
resolver.Search = resolver.Search[:maxSearchDomains]
}
return nil
}
func getConfiguredResolvers(list []string) (resolvers []*Resolver) {
for _, server := range list {
resolver, skip, err := createResolver(server, ServerSourceConfigured)
if err != nil {
// TODO(ppacher): module error
log.Errorf("cannot use resolver %s: %s", server, err)
continue
}
if skip {
continue
}
resolvers = append(resolvers, resolver)
}
return resolvers
}
func getSystemResolvers() (resolvers []*Resolver) {
for _, nameserver := range netenv.Nameservers() {
serverURL := fmt.Sprintf("dns://%s", formatIPAndPort(nameserver.IP, 53))
resolver, skip, err := createResolver(serverURL, ServerSourceOperatingSystem)
if err != nil {
// that shouldn't happen but handle it anyway ...
log.Errorf("cannot use system resolver %s: %s", serverURL, err)
continue
}
if skip {
continue
}
if resolver.Info.IPScope.IsLAN() {
_ = configureSearchDomains(resolver, nameserver.Search, false)
}
resolvers = append(resolvers, resolver)
}
return resolvers
}
const missingResolversErrorID = "missing-resolvers"
func loadResolvers() {
defer func() {
if !allConfiguredResolversAreFailing() {
resetFailingResolversNotification()
}
}()
// TODO: what happens when a lot of processes want to reload at once? we do not need to run this multiple times in a short time frame.
resolversLock.Lock()
defer resolversLock.Unlock()
// Resolve module error about missing resolvers.
module.Resolve(missingResolversErrorID)
// Check if settings were changed and clear name cache when they did.
newResolverConfig := configuredNameServers()
if len(currentResolverConfig) > 0 &&
!utils.StringSliceEqual(currentResolverConfig, newResolverConfig) {
module.StartWorker("clear dns cache", func(ctx context.Context) error {
log.Info("resolver: clearing dns cache due to changed resolver config")
_, err := clearNameCache(ctx)
return err
})
}
currentResolverConfig = newResolverConfig
newResolvers := append(
getConfiguredResolvers(newResolverConfig),
getSystemResolvers()...,
)
if len(newResolvers) == 0 {
// load defaults directly, overriding config system
newResolvers = getConfiguredResolvers(defaultNameServers)
if len(newResolvers) > 0 {
log.Warning("resolver: no (valid) dns server found in config or system, falling back to global defaults")
module.Warning(
missingResolversErrorID,
"Using Factory Default DNS Servers",
"The Portmaster could not find any (valid) DNS servers in the settings or system. In order to prevent being disconnected, the factory defaults are being used instead. If you just switched your network, this should be resolved shortly.",
)
} else {
log.Critical("resolver: no (valid) dns server found in config, system or global defaults")
module.Error(
missingResolversErrorID,
"No DNS Servers Configured",
"The Portmaster could not find any (valid) DNS servers in the settings or system. You will experience severe connectivity problems until resolved. If you just switched your network, this should be resolved shortly.",
)
}
}
// save resolvers
globalResolvers = newResolvers
// assing resolvers to scopes
setScopedResolvers(globalResolvers)
// set active resolvers (for cache validation)
// reset
activeResolvers = make(map[string]*Resolver)
// add
for _, resolver := range newResolvers {
activeResolvers[resolver.Info.ID()] = resolver
}
activeResolvers[mDNSResolver.Info.ID()] = mDNSResolver
activeResolvers[envResolver.Info.ID()] = envResolver
// log global resolvers
if len(globalResolvers) > 0 {
log.Trace("resolver: loaded global resolvers:")
for _, resolver := range globalResolvers {
log.Tracef("resolver: %s", resolver.ConfigURL)
}
} else {
log.Warning("resolver: no global resolvers loaded")
}
// log local resolvers
if len(localResolvers) > 0 {
log.Trace("resolver: loaded local resolvers:")
for _, resolver := range localResolvers {
log.Tracef("resolver: %s", resolver.ConfigURL)
}
} else {
log.Info("resolver: no local resolvers loaded")
}
// log system resolvers
if len(systemResolvers) > 0 {
log.Trace("resolver: loaded system/network-assigned resolvers:")
for _, resolver := range systemResolvers {
log.Tracef("resolver: %s", resolver.ConfigURL)
}
} else {
log.Info("resolver: no system/network-assigned resolvers loaded")
}
// log scopes
if len(localScopes) > 0 {
log.Trace("resolver: loaded scopes:")
for _, scope := range localScopes {
var scopeServers []string
for _, resolver := range scope.Resolvers {
scopeServers = append(scopeServers, resolver.ConfigURL)
}
log.Tracef("resolver: %s: %s", scope.Domain, strings.Join(scopeServers, ", "))
}
} else {
log.Info("resolver: no scopes loaded")
}
// alert if no resolvers are loaded
if len(globalResolvers) == 0 && len(localResolvers) == 0 {
log.Critical("resolver: no resolvers loaded!")
}
}
func setScopedResolvers(resolvers []*Resolver) {
// make list with local resolvers
localResolvers = make([]*Resolver, 0)
systemResolvers = make([]*Resolver, 0)
localScopes = make([]*Scope, 0)
for _, resolver := range resolvers {
if resolver.Info.IPScope.IsLAN() {
localResolvers = append(localResolvers, resolver)
} else if _, err := netenv.GetLocalNetwork(resolver.Info.IP); err != nil {
localResolvers = append(localResolvers, resolver)
}
if resolver.Info.Source == ServerSourceOperatingSystem {
systemResolvers = append(systemResolvers, resolver)
}
if resolver.Search != nil {
// add resolver to custom searches
for _, search := range resolver.Search {
if search == "." {
continue
}
key := indexOfScope(search, localScopes)
if key == -1 {
localScopes = append(localScopes, &Scope{
Domain: search,
Resolvers: []*Resolver{resolver},
})
continue
}
localScopes[key].Resolvers = append(localScopes[key].Resolvers, resolver)
}
}
}
// sort scopes by length
sort.Slice(localScopes,
func(i, j int) bool {
return len(localScopes[i].Domain) > len(localScopes[j].Domain)
},
)
}
func checkSearchScope(searchDomain string) error {
// Sanity check the input.
if len(searchDomain) == 0 ||
searchDomain[0] == '.' ||
searchDomain[len(searchDomain)-1] == '.' {
return fmt.Errorf("invalid (1) search domain: %s", searchDomain)
}
// Domains that are specifically listed in the special service domains may be
// diverted completely.
if domainInScope("."+searchDomain+".", specialServiceDomains) {
return nil
}
// In order to check if the search domain is too high up in the hierarchy, we
// need to add some more subdomains.
augmentedSearchDomain := "*.*.*.*.*." + searchDomain
// Get the public suffix of the search domain and if the TLD is managed by ICANN.
suffix, icann := publicsuffix.PublicSuffix(augmentedSearchDomain)
// Sanity check the result.
if len(suffix) == 0 {
return fmt.Errorf("invalid (2) search domain: %s", searchDomain)
}
// TLDs that are not managed by ICANN (ie. are unofficial) may be
// diverted completely.
// This might include special service domains like ".onion" and ".bit".
if !icann && !strings.Contains(suffix, ".") {
return nil
}
// Build the eTLD+1 domain, which is the highest that may be diverted.
split := len(augmentedSearchDomain) - len(suffix) - 1
eTLDplus1 := augmentedSearchDomain[1+strings.LastIndex(augmentedSearchDomain[:split], "."):]
// Check if the scope is being violated by checking if the eTLD+1 contains a wildcard.
if strings.Contains(eTLDplus1, "*") {
return fmt.Errorf(`search domain "%s" is dangerously high up the hierarchy, stay at or below "%s"`, searchDomain, eTLDplus1)
}
return nil
}
// IsResolverAddress returns whether the given ip and port match a configured resolver.
func IsResolverAddress(ip net.IP, port uint16) bool {
resolversLock.RLock()
defer resolversLock.RUnlock()
// Check if the given IP and port matches a resolver.
for _, r := range globalResolvers {
if port == r.Info.Port && r.Info.IP.Equal(ip) {
return true
}
}
return false
}
// ForceResolverReconnect forces all resolvers to reconnect.
func ForceResolverReconnect(ctx context.Context) {
resolversLock.RLock()
defer resolversLock.RUnlock()
ctx, tracer := log.AddTracer(ctx)
defer tracer.Submit()
tracer.Trace("resolver: forcing all active resolvers to reconnect")
for _, r := range globalResolvers {
r.Conn.ForceReconnect(ctx)
}
tracer.Info("resolver: all active resolvers were forced to reconnect")
}
// allConfiguredResolversAreFailing reports whether all configured resolvers are failing.
// Return false if there are no configured resolvers.
func allConfiguredResolversAreFailing() bool {
resolversLock.RLock()
defer resolversLock.RUnlock()
// If there are no configured resolvers, return as not failing.
if len(currentResolverConfig) == 0 {
return false
}
// Return as not failing, if we can find any non-failing configured resolver.
for _, resolver := range globalResolvers {
if !resolver.Conn.IsFailing() && resolver.Info.Source == ServerSourceConfigured {
// We found a non-failing configured resolver.
return false
}
}
return true
}