forked from AdguardTeam/dnsproxy
/
proxy_cache.go
112 lines (93 loc) · 2.99 KB
/
proxy_cache.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
package proxy
import (
"net"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
)
// replyFromCache tries to get the response from general or subnet cache.
// Returns true on success.
func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
var ci *cacheItem
var hitMsg string
var expired bool
var key []byte
if !p.Config.EnableEDNSClientSubnet {
ci, expired, key = p.cache.get(d.Req)
hitMsg = "serving cached response"
} else if d.ReqECS != nil {
ci, expired, key = p.cache.getWithSubnet(d.Req, d.ReqECS)
hitMsg = "serving response from subnet cache"
} else {
ci, expired, key = p.cache.get(d.Req)
hitMsg = "serving response from general cache"
}
if hit = ci != nil; !hit {
return hit
}
d.Res = ci.m
d.CachedUpstreamAddr = ci.u
log.Debug("dnsproxy: cache: %s", hitMsg)
if p.cache.optimistic && expired {
// Build a reduced clone of the current context to avoid data race.
minCtxClone := &DNSContext{
// It is only read inside the optimistic resolver.
CustomUpstreamConfig: d.CustomUpstreamConfig,
ReqECS: netutil.CloneIPNet(d.ReqECS),
}
if d.Req != nil {
minCtxClone.Req = d.Req.Copy()
addDO(minCtxClone.Req)
}
go p.shortFlighter.ResolveOnce(minCtxClone, key)
}
return hit
}
// cacheResp stores the response from d in general or subnet cache.
func (p *Proxy) cacheResp(d *DNSContext) {
if !p.EnableEDNSClientSubnet {
p.cache.set(d.Res, d.Upstream)
return
}
switch ecs, scope := ecsFromMsg(d.Res); {
case ecs != nil && d.ReqECS != nil:
ones, bits := ecs.Mask.Size()
reqOnes, _ := d.ReqECS.Mask.Size()
// If FAMILY, SOURCE PREFIX-LENGTH, and SOURCE PREFIX-LENGTH bits of
// ADDRESS in the response don't match the non-zero fields in the
// corresponding query, the full response MUST be dropped.
//
// See RFC 7871 Section 7.3.
//
// TODO(a.meshkov): The whole response MUST be dropped if ECS in it
// doesn't correspond.
if !ecs.IP.Mask(ecs.Mask).Equal(d.ReqECS.IP.Mask(d.ReqECS.Mask)) || ones != reqOnes {
log.Debug("dnsproxy: cache: bad response: ecs %s does not match %s", ecs, d.ReqECS)
return
}
// If SCOPE PREFIX-LENGTH is not longer than SOURCE PREFIX-LENGTH, store
// SCOPE PREFIX-LENGTH bits of ADDRESS, and then mark the response as
// valid for all addresses that fall within that range.
//
// See RFC 7871 Section 7.3.1.
if scope < reqOnes {
ecs.Mask = net.CIDRMask(scope, bits)
ecs.IP = ecs.IP.Mask(ecs.Mask)
}
log.Debug("dnsproxy: cache: ecs option in response: %s", ecs)
p.cache.setWithSubnet(d.Res, d.Upstream, ecs)
case d.ReqECS != nil:
// Cache the response for all subnets since the server doesn't support
// EDNS Client Subnet option.
p.cache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
default:
p.cache.set(d.Res, d.Upstream)
}
}
// ClearCache clears the DNS cache of p.
func (p *Proxy) ClearCache() {
if p.cache != nil {
p.cache.clearItems()
p.cache.clearItemsWithSubnet()
log.Debug("dnsproxy: cache: cleared")
}
}