Skip to content

Commit a358f2d

Browse files
lifenjoinerjedisct1
authored andcommitted
Improve target CNAME resolving for cloaking (#2959)
- Fix critical privacy leak: Replace system DNS lookups with encrypted resolution - Prevent cloaking rule bypass when upstream server fails - Fix IPv4/IPv6 filtering to handle both types correctly - Add error handling for empty IP lists before slicing - Separate caching for IPv4 and IPv6 responses - Add proper timeout for resolver queries - Fix systemd socket mode to properly populate listenAddresses - Extend read locking scope in PluginCloak
1 parent afab24a commit a358f2d

4 files changed

Lines changed: 135 additions & 101 deletions

File tree

dnscrypt-proxy/plugin_cloak.go

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import (
1515
)
1616

1717
type CloakedName struct {
18-
target string
19-
ipv4 []net.IP
20-
ipv6 []net.IP
21-
lastUpdate *time.Time
22-
lineNo int
23-
isIP bool
24-
PTR []string
18+
target string
19+
ipv4 []net.IP
20+
ipv6 []net.IP
21+
lastUpdate4 *time.Time
22+
lastUpdate6 *time.Time
23+
lineNo int
24+
isIP bool
25+
PTR []string
2526
}
2627

2728
type PluginCloak struct {
@@ -242,47 +243,52 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error
242243
}
243244
cloakedName := xcloakedName.(*CloakedName)
244245
ttl, expired := plugin.ttl, false
245-
if cloakedName.lastUpdate != nil {
246-
if elapsed := uint32(now.Sub(*cloakedName.lastUpdate).Seconds()); elapsed < ttl {
246+
var lastUpdate *time.Time
247+
switch question.Qtype {
248+
case dns.TypeA:
249+
lastUpdate = cloakedName.lastUpdate4
250+
case dns.TypeAAAA:
251+
lastUpdate = cloakedName.lastUpdate6
252+
}
253+
if lastUpdate != nil {
254+
if elapsed := uint32(now.Sub(*lastUpdate).Seconds()); elapsed < ttl {
247255
ttl -= elapsed
248256
} else {
249257
expired = true
250258
}
251259
}
252-
if !cloakedName.isIP && ((cloakedName.ipv4 == nil && cloakedName.ipv6 == nil) || expired) {
260+
synth := EmptyResponseFromMessage(msg)
261+
if !cloakedName.isIP && ((question.Qtype == dns.TypeA && cloakedName.ipv4 == nil) ||
262+
(question.Qtype == dns.TypeAAAA && cloakedName.ipv6 == nil) || expired) {
253263
target := cloakedName.target
254264
plugin.RUnlock()
255-
foundIPs, err := net.LookupIP(target)
265+
foundIPs, _, err := pluginsState.xTransport.resolveEncrypted(target, question.Qtype)
256266
if err != nil {
267+
synth.Rcode = dns.RcodeServerFailure
268+
pluginsState.synthResponse = synth
269+
pluginsState.action = PluginsActionSynth
270+
pluginsState.returnCode = PluginsReturnCodeCloak
257271
return nil
258272
}
259273

260274
// Use write lock to update cloakedName
261275
plugin.Lock()
262-
cloakedName.lastUpdate = &now
263-
cloakedName.ipv4 = nil
264-
cloakedName.ipv6 = nil
265-
for _, foundIP := range foundIPs {
266-
if ipv4 := foundIP.To4(); ipv4 != nil {
267-
cloakedName.ipv4 = append(cloakedName.ipv4, foundIP)
268-
if len(cloakedName.ipv4) >= 16 {
269-
break
270-
}
271-
} else {
272-
cloakedName.ipv6 = append(cloakedName.ipv6, foundIP)
273-
if len(cloakedName.ipv6) >= 16 {
274-
break
275-
}
276+
if len(foundIPs) > 0 {
277+
n := Min(16, len(foundIPs))
278+
switch question.Qtype {
279+
case dns.TypeA:
280+
cloakedName.lastUpdate4 = &now
281+
cloakedName.ipv4 = foundIPs[:n]
282+
case dns.TypeAAAA:
283+
cloakedName.lastUpdate6 = &now
284+
cloakedName.ipv6 = foundIPs[:n]
276285
}
277286
}
278287
plugin.Unlock()
279288

280289
// Reacquire read lock
281290
plugin.RLock()
282291
}
283-
plugin.RUnlock()
284-
285-
synth := EmptyResponseFromMessage(msg)
286292
synth.Answer = []dns.RR{}
287293
if question.Qtype == dns.TypeA {
288294
for _, ip := range cloakedName.ipv4 {
@@ -306,6 +312,8 @@ func (plugin *PluginCloak) Eval(pluginsState *PluginsState, msg *dns.Msg) error
306312
synth.Answer = append(synth.Answer, rr)
307313
}
308314
}
315+
plugin.RUnlock()
316+
309317
rand.Shuffle(
310318
len(synth.Answer),
311319
func(i, j int) { synth.Answer[i], synth.Answer[j] = synth.Answer[j], synth.Answer[i] },

dnscrypt-proxy/plugins.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type PluginsState struct {
7575
clientAddr *net.Addr
7676
synthResponse *dns.Msg
7777
questionMsg *dns.Msg
78+
xTransport *XTransport
7879
sessionData map[string]interface{}
7980
action PluginsAction
8081
timeout time.Duration
@@ -267,6 +268,7 @@ func NewPluginsState(
267268
requestStart: start,
268269
maxUnencryptedUDPSafePayloadSize: MaxDNSUDPSafePacketSize,
269270
sessionData: make(map[string]interface{}),
271+
xTransport: proxy.xTransport,
270272
}
271273
}
272274

dnscrypt-proxy/systemd_linux.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package main
44

55
import (
66
"net"
7+
"slices"
78

89
"github.com/coreos/go-systemd/activation"
910
"github.com/jedisct1/dlog"
@@ -19,15 +20,22 @@ func (proxy *Proxy) addSystemDListeners() error {
1920
)
2021
}
2122
dlog.Warn("Systemd sockets are untested and unsupported - use at your own risk")
23+
proxy.listenAddresses = make([]string, 0)
2224
}
2325
for i, file := range files {
2426
defer file.Close()
27+
var listenAddress string
2528
if listener, err := net.FileListener(file); err == nil {
2629
proxy.registerTCPListener(listener.(*net.TCPListener))
27-
dlog.Noticef("Wiring systemd TCP socket #%d, %s, %s", i, file.Name(), listener.Addr())
30+
listenAddress = listener.Addr().String()
31+
dlog.Noticef("Wiring systemd TCP socket #%d, %s, %s", i, file.Name(), listenAddress)
2832
} else if pc, err := net.FilePacketConn(file); err == nil {
2933
proxy.registerUDPListener(pc.(*net.UDPConn))
30-
dlog.Noticef("Wiring systemd UDP socket #%d, %s, %s", i, file.Name(), pc.LocalAddr())
34+
listenAddress = pc.LocalAddr().String()
35+
dlog.Noticef("Wiring systemd UDP socket #%d, %s, %s", i, file.Name(), listenAddress)
36+
}
37+
if len(listenAddress) > 0 && !slices.Contains(proxy.listenAddresses, listenAddress) {
38+
proxy.listenAddresses = append(proxy.listenAddresses, listenAddress)
3139
}
3240
}
3341
return nil

dnscrypt-proxy/xtransport.go

Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
DefaultBootstrapResolver = "9.9.9.9:53"
3535
DefaultKeepAlive = 5 * time.Second
3636
DefaultTimeout = 30 * time.Second
37+
ResolverReadTimeout = 5 * time.Second
3738
SystemResolverIPTTL = 12 * time.Hour
3839
MinResolverIPTTL = 4 * time.Hour
3940
ResolverIPTTLMaxJitter = 15 * time.Minute
@@ -331,83 +332,88 @@ func (xTransport *XTransport) rebuildTransport() {
331332
}
332333
}
333334

334-
func (xTransport *XTransport) resolveUsingSystem(host string) (ip net.IP, ttl time.Duration, err error) {
335-
ttl = SystemResolverIPTTL
336-
var foundIPs []string
337-
foundIPs, err = net.LookupHost(host)
338-
if err != nil {
339-
return ip, ttl, err
335+
func (xTransport *XTransport) resolveUsingSystem(host string, forceType uint16) ([]net.IP, time.Duration, error) {
336+
ipa, err := net.LookupIP(host)
337+
queryIPv4 := xTransport.useIPv4
338+
queryIPv6 := xTransport.useIPv6
339+
if forceType == dns.TypeNone && queryIPv4 && queryIPv6 {
340+
return ipa, SystemResolverIPTTL, err
341+
}
342+
switch forceType {
343+
case dns.TypeA:
344+
queryIPv4 = true
345+
queryIPv6 = false
346+
case dns.TypeAAAA:
347+
queryIPv4 = false
348+
queryIPv6 = true
340349
}
341350
ips := make([]net.IP, 0)
342-
for _, ip := range foundIPs {
343-
if foundIP := net.ParseIP(ip); foundIP != nil {
344-
isIPv4 := foundIP.To4() != nil
345-
if (isIPv4 && xTransport.useIPv4) || (!isIPv4 && xTransport.useIPv6) {
346-
ips = append(ips, foundIP)
347-
}
351+
for _, ip := range ipa {
352+
ipv4 := ip.To4()
353+
if queryIPv4 && ipv4 != nil {
354+
ips = append(ips, ipv4)
355+
}
356+
if queryIPv6 && ipv4 == nil {
357+
ips = append(ips, ip)
348358
}
349359
}
350-
if len(ips) > 0 {
351-
ip = ips[rand.Intn(len(ips))]
352-
}
353-
return ip, ttl, err
360+
return ips, SystemResolverIPTTL, err
354361
}
355362

356363
func (xTransport *XTransport) resolveUsingResolver(
357364
proto, host string,
358365
resolver string,
359-
) (ip net.IP, ttl time.Duration, err error) {
360-
dnsClient := dns.Client{Net: proto}
361-
if xTransport.useIPv4 {
362-
msg := dns.Msg{}
363-
msg.SetQuestion(dns.Fqdn(host), dns.TypeA)
364-
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
365-
var in *dns.Msg
366-
if in, _, err = dnsClient.Exchange(&msg, resolver); err == nil {
367-
answers := make([]dns.RR, 0)
368-
for _, answer := range in.Answer {
369-
if answer.Header().Rrtype == dns.TypeA {
370-
answers = append(answers, answer)
371-
}
372-
}
373-
if len(answers) > 0 {
374-
answer := answers[rand.Intn(len(answers))]
375-
ip = answer.(*dns.A).A
376-
ttl = time.Duration(answer.Header().Ttl) * time.Second
377-
return ip, ttl, err
378-
}
366+
forceType uint16,
367+
) (ips []net.IP, ttl time.Duration, err error) {
368+
dnsClient := dns.Client{Net: proto, ReadTimeout: ResolverReadTimeout}
369+
queryType := make([]uint16, 0, 2)
370+
switch forceType {
371+
case dns.TypeA:
372+
queryType = append(queryType, dns.TypeA)
373+
case dns.TypeAAAA:
374+
queryType = append(queryType, dns.TypeAAAA)
375+
default:
376+
if xTransport.useIPv4 {
377+
queryType = append(queryType, dns.TypeA)
378+
}
379+
if xTransport.useIPv6 {
380+
queryType = append(queryType, dns.TypeAAAA)
379381
}
380382
}
381-
if xTransport.useIPv6 {
383+
var rrTTL uint32
384+
for _, rrType := range queryType {
382385
msg := dns.Msg{}
383-
msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
386+
msg.SetQuestion(dns.Fqdn(host), rrType)
384387
msg.SetEdns0(uint16(MaxDNSPacketSize), true)
385388
var in *dns.Msg
386389
if in, _, err = dnsClient.Exchange(&msg, resolver); err == nil {
387-
answers := make([]dns.RR, 0)
388390
for _, answer := range in.Answer {
389-
if answer.Header().Rrtype == dns.TypeAAAA {
390-
answers = append(answers, answer)
391+
if answer.Header().Rrtype == rrType {
392+
switch rrType {
393+
case dns.TypeA:
394+
ips = append(ips, answer.(*dns.A).A)
395+
case dns.TypeAAAA:
396+
ips = append(ips, answer.(*dns.AAAA).AAAA)
397+
}
398+
rrTTL = answer.Header().Ttl
391399
}
392400
}
393-
if len(answers) > 0 {
394-
answer := answers[rand.Intn(len(answers))]
395-
ip = answer.(*dns.AAAA).AAAA
396-
ttl = time.Duration(answer.Header().Ttl) * time.Second
397-
return ip, ttl, err
398-
}
399401
}
400402
}
401-
return ip, ttl, err
403+
if len(ips) > 0 {
404+
ttl = time.Duration(rrTTL) * time.Second
405+
}
406+
return ips, ttl, err
402407
}
403408

404409
func (xTransport *XTransport) resolveUsingResolvers(
405410
proto, host string,
406411
resolvers []string,
407-
) (ip net.IP, ttl time.Duration, err error) {
412+
forceType uint16,
413+
) (ips []net.IP, ttl time.Duration, err error) {
408414
err = errors.New("Empty resolvers")
409415
for i, resolver := range resolvers {
410-
ip, ttl, err = xTransport.resolveUsingResolver(proto, host, resolver)
416+
ips, ttl, err = xTransport.resolveUsingResolver(proto, host, resolver, forceType)
411417
if err == nil {
412418
if i > 0 {
413419
dlog.Infof("Resolution succeeded with resolver %s[%s]", proto, resolver)
@@ -417,34 +423,22 @@ func (xTransport *XTransport) resolveUsingResolvers(
417423
}
418424
dlog.Infof("Unable to resolve [%s] using resolver [%s] (%s): %v", host, resolver, proto, err)
419425
}
420-
return ip, ttl, err
426+
return ips, ttl, err
421427
}
422428

423-
// If a name is not present in the cache, resolve the name and update the cache
424-
func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
425-
if xTransport.proxyDialer != nil || xTransport.httpProxyFunction != nil {
426-
return nil
427-
}
428-
if ParseIP(host) != nil {
429-
return nil
430-
}
431-
cachedIP, expired, updating := xTransport.loadCachedIP(host)
432-
if cachedIP != nil && (!expired || updating) {
433-
return nil
434-
}
435-
xTransport.markUpdatingCachedIP(host)
429+
func (xTransport *XTransport) resolveEncrypted(host string, forceType uint16) ([]net.IP, time.Duration, error) {
430+
return xTransport.resolveUsingResolvers(xTransport.mainProto, host, xTransport.internalResolvers, forceType)
431+
}
436432

437-
var foundIP net.IP
438-
var ttl time.Duration
439-
var err error
433+
func (xTransport *XTransport) resolve(host string, forceType uint16) (ips []net.IP, ttl time.Duration, err error) {
440434
protos := []string{"udp", "tcp"}
441435
if xTransport.mainProto == "tcp" {
442436
protos = []string{"tcp", "udp"}
443437
}
444438
if xTransport.ignoreSystemDNS {
445439
if xTransport.internalResolverReady {
446440
for _, proto := range protos {
447-
foundIP, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.internalResolvers)
441+
ips, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.internalResolvers, forceType)
448442
if err == nil {
449443
break
450444
}
@@ -454,7 +448,7 @@ func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
454448
dlog.Notice(err)
455449
}
456450
} else {
457-
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
451+
ips, ttl, err = xTransport.resolveUsingSystem(host, forceType)
458452
if err != nil {
459453
err = errors.New("System DNS is not usable yet")
460454
dlog.Notice(err)
@@ -469,15 +463,37 @@ func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
469463
proto,
470464
)
471465
}
472-
foundIP, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.bootstrapResolvers)
466+
ips, ttl, err = xTransport.resolveUsingResolvers(proto, host, xTransport.bootstrapResolvers, forceType)
473467
if err == nil {
474468
break
475469
}
476470
}
477471
}
478472
if err != nil && xTransport.ignoreSystemDNS {
479473
dlog.Noticef("Bootstrap resolvers didn't respond - Trying with the system resolver as a last resort")
480-
foundIP, ttl, err = xTransport.resolveUsingSystem(host)
474+
ips, ttl, err = xTransport.resolveUsingSystem(host, forceType)
475+
}
476+
return ips, ttl, err
477+
}
478+
479+
// If a name is not present in the cache, resolve the name and update the cache
480+
func (xTransport *XTransport) resolveAndUpdateCache(host string) error {
481+
if xTransport.proxyDialer != nil || xTransport.httpProxyFunction != nil {
482+
return nil
483+
}
484+
if ParseIP(host) != nil {
485+
return nil
486+
}
487+
cachedIP, expired, updating := xTransport.loadCachedIP(host)
488+
if cachedIP != nil && (!expired || updating) {
489+
return nil
490+
}
491+
xTransport.markUpdatingCachedIP(host)
492+
493+
var foundIP net.IP
494+
ips, ttl, err := xTransport.resolve(host, dns.TypeNone)
495+
if len(ips) > 0 {
496+
foundIP = ips[rand.Intn(len(ips))]
481497
}
482498
if ttl < MinResolverIPTTL {
483499
ttl = MinResolverIPTTL

0 commit comments

Comments
 (0)