/
proxycache.go
140 lines (115 loc) · 3.71 KB
/
proxycache.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
package proxy
import (
"net"
"slices"
"github.com/AdguardTeam/golibs/log"
)
// cacheForContext returns cache object for the given context.
func (p *Proxy) cacheForContext(d *DNSContext) (c *cache) {
if d.CustomUpstreamConfig != nil && d.CustomUpstreamConfig.cache != nil {
return d.CustomUpstreamConfig.cache
}
return p.cache
}
// replyFromCache tries to get the response from general or subnet cache. In
// case the cache is present in d, it's used first. Returns true on success.
func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
dctxCache := p.cacheForContext(d)
var ci *cacheItem
var hitMsg string
var expired bool
var key []byte
// TODO(d.kolyshev): Use EnableEDNSClientSubnet from dctxCache.
if !p.Config.EnableEDNSClientSubnet {
ci, expired, key = dctxCache.get(d.Req)
hitMsg = "serving cached response"
} else if d.ReqECS != nil {
ci, expired, key = dctxCache.getWithSubnet(d.Req, d.ReqECS)
hitMsg = "serving response from subnet cache"
} else {
ci, expired, key = dctxCache.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 dctxCache.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: cloneIPNet(d.ReqECS),
IsPrivateClient: d.IsPrivateClient,
}
if d.Req != nil {
minCtxClone.Req = d.Req.Copy()
addDO(minCtxClone.Req)
}
go p.shortFlighter.ResolveOnce(minCtxClone, key)
}
return hit
}
// cloneIPNet returns a deep clone of n.
func cloneIPNet(n *net.IPNet) (clone *net.IPNet) {
if n == nil {
return nil
}
return &net.IPNet{
IP: slices.Clone(n.IP),
Mask: slices.Clone(n.Mask),
}
}
// cacheResp stores the response from d in general or subnet cache. In case the
// cache is present in d, it's used first.
func (p *Proxy) cacheResp(d *DNSContext) {
dctxCache := p.cacheForContext(d)
if !p.EnableEDNSClientSubnet {
dctxCache.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)
dctxCache.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.
dctxCache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
default:
dctxCache.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")
}
}