Skip to content

Commit 8f0cbea

Browse files
committed
Pull request: proxy: custom upstream cache
Merge in GO/dnsproxy from custom-ups-cache to master Squashed commit of the following: commit 26ee425 Author: Dimitry Kolyshev <[email protected]> Date: Tue Nov 14 10:22:17 2023 +0200 proxy: custom upstream cache commit ae2f902 Author: Dimitry Kolyshev <[email protected]> Date: Mon Nov 13 14:31:28 2023 +0200 proxy: imp docs commit 6991d32 Author: Dimitry Kolyshev <[email protected]> Date: Mon Nov 13 10:29:57 2023 +0200 proxy: imp code commit 34662cc Merge: cb80501 65b5293 Author: Dimitry Kolyshev <[email protected]> Date: Mon Nov 13 10:10:41 2023 +0200 Merge remote-tracking branch 'origin/master' into custom-ups-cache commit cb80501 Merge: a933441 ea0f2b1 Author: Dimitry Kolyshev <[email protected]> Date: Fri Nov 10 15:32:25 2023 +0200 Merge remote-tracking branch 'origin/master' into custom-ups-cache commit a933441 Author: Dimitry Kolyshev <[email protected]> Date: Fri Nov 10 15:17:19 2023 +0200 proxy: imp docs commit 1cff28e Author: Dimitry Kolyshev <[email protected]> Date: Fri Nov 10 14:31:16 2023 +0200 proxy: imp code commit 62fed0c Author: Dimitry Kolyshev <[email protected]> Date: Fri Nov 10 14:07:20 2023 +0200 proxy: imp code commit f1aaa42 Merge: d6e3a57 b490fb4 Author: Dimitry Kolyshev <[email protected]> Date: Thu Nov 9 12:04:13 2023 +0200 Merge remote-tracking branch 'origin/master' into custom-ups-cache commit d6e3a57 Author: Dimitry Kolyshev <[email protected]> Date: Wed Nov 8 12:11:43 2023 +0200 proxy: imp code commit 0b11894 Author: Dimitry Kolyshev <[email protected]> Date: Wed Nov 8 10:37:28 2023 +0200 proxy: imp code commit 1775ae9 Author: Dimitry Kolyshev <[email protected]> Date: Tue Nov 7 14:44:03 2023 +0200 proxy: custom upstream cache
1 parent 65b5293 commit 8f0cbea

File tree

4 files changed

+169
-22
lines changed

4 files changed

+169
-22
lines changed

proxy/dnscontext.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ type DNSContext struct {
4747
// ReqECS is the EDNS Client Subnet used in the request.
4848
ReqECS *net.IPNet
4949

50-
// CustomUpstreamConfig is only used for current request. The Resolve
51-
// method of Proxy uses it instead of the default servers if it's not nil.
52-
CustomUpstreamConfig *UpstreamConfig
50+
// CustomUpstreamConfig is the upstreams configuration used only for current
51+
// request. The Resolve method of Proxy uses it instead of the default
52+
// servers if it's not nil.
53+
CustomUpstreamConfig *CustomUpstreamConfig
5354

5455
// Req is the request message.
5556
Req *dns.Msg
@@ -157,3 +158,53 @@ const (
157158
// DoQv1 represents DoQ v1.0: https://www.rfc-editor.org/rfc/rfc9250.html.
158159
DoQv1 DoQVersion = 0x01
159160
)
161+
162+
// CustomUpstreamConfig contains upstreams configuration with an optional cache.
163+
type CustomUpstreamConfig struct {
164+
// upstream is the upstream configuration.
165+
upstream *UpstreamConfig
166+
167+
// cache is an optional cache for upstreams in the current configuration.
168+
// It is disabled if nil.
169+
//
170+
// TODO(d.kolyshev): Move this cache to [UpstreamConfig].
171+
cache *cache
172+
}
173+
174+
// NewCustomUpstreamConfig returns new custom upstream configuration.
175+
func NewCustomUpstreamConfig(
176+
u *UpstreamConfig,
177+
cacheEnabled bool,
178+
cacheSize int,
179+
enableEDNSClientSubnet bool,
180+
) (c *CustomUpstreamConfig) {
181+
var customCache *cache
182+
if cacheEnabled {
183+
// TODO(d.kolyshev): Support optimistic with newOptimisticResolver.
184+
customCache = newCache(cacheSize, enableEDNSClientSubnet, false)
185+
}
186+
187+
return &CustomUpstreamConfig{
188+
upstream: u,
189+
cache: customCache,
190+
}
191+
}
192+
193+
// Close closes the custom upstream config.
194+
func (c *CustomUpstreamConfig) Close() (err error) {
195+
if c.upstream == nil {
196+
return nil
197+
}
198+
199+
return c.upstream.Close()
200+
}
201+
202+
// ClearCache removes all items from the cache.
203+
func (c *CustomUpstreamConfig) ClearCache() {
204+
if c.cache == nil {
205+
return
206+
}
207+
208+
c.cache.clearItems()
209+
c.cache.clearItemsWithSubnet()
210+
}

proxy/proxy.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ type Proxy struct {
139139
// --
140140

141141
// cache is used to cache requests. It is disabled if nil.
142+
//
143+
// TODO(d.kolyshev): Move this cache to [Proxy.UpstreamConfig] field.
142144
cache *cache
143145

144146
// shortFlighter is used to resolve the expired cached requests without
@@ -494,7 +496,7 @@ func (p *Proxy) selectUpstreams(d *DNSContext) (upstreams []upstream.Upstream) {
494496

495497
if custom := d.CustomUpstreamConfig; custom != nil {
496498
// Try to use custom.
497-
upstreams = getUpstreams(custom, host)
499+
upstreams = getUpstreams(custom.upstream, host)
498500
if len(upstreams) > 0 {
499501
return upstreams
500502
}
@@ -622,7 +624,6 @@ func (p *Proxy) Resolve(dctx *DNSContext) (err error) {
622624

623625
dctx.calcFlagsAndSize()
624626

625-
// Use cache only if it's enabled and the query doesn't use custom upstream.
626627
// Also don't lookup the cache for responses with DNSSEC checking disabled
627628
// since only validated responses are cached and those may be not the
628629
// desired result for user specifying CD flag.
@@ -676,9 +677,12 @@ func (p *Proxy) cacheWorks(dctx *DNSContext) (ok bool) {
676677
switch {
677678
case p.cache == nil:
678679
reason = "disabled"
679-
case dctx.CustomUpstreamConfig != nil:
680+
case dctx.CustomUpstreamConfig != nil && dctx.CustomUpstreamConfig.cache == nil:
681+
// In case of custom upstream cache is not configured, the global proxy
682+
// cache cannot be used because different upstreams can return different
683+
// results.
680684
// See https://github.com/AdguardTeam/dnsproxy/issues/169.
681-
reason = "custom upstreams used"
685+
reason = "custom upstreams cache is not configured"
682686
case dctx.Req.CheckingDisabled:
683687
reason = "dnssec check disabled"
684688
default:

proxy/proxy_test.go

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -846,8 +846,13 @@ func TestProxy_ReplyFromUpstream_badResponse(t *testing.T) {
846846
}
847847

848848
d := &DNSContext{
849-
CustomUpstreamConfig: &UpstreamConfig{Upstreams: []upstream.Upstream{u}},
850-
Req: createHostTestMessage("host"),
849+
CustomUpstreamConfig: NewCustomUpstreamConfig(
850+
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
851+
false,
852+
0,
853+
false,
854+
),
855+
Req: createHostTestMessage("host"),
851856
Addr: &net.TCPAddr{
852857
IP: net.IP{1, 2, 3, 0},
853858
},
@@ -869,7 +874,7 @@ func TestExchangeCustomUpstreamConfig(t *testing.T) {
869874
testutil.CleanupAndRequireSuccess(t, prx.Stop)
870875

871876
ansIP := net.IP{4, 3, 2, 1}
872-
u := testUpstream{
877+
u := &testUpstream{
873878
ans: []dns.RR{&dns.A{
874879
Hdr: dns.RR_Header{
875880
Rrtype: dns.TypeA,
@@ -881,14 +886,86 @@ func TestExchangeCustomUpstreamConfig(t *testing.T) {
881886
}
882887

883888
d := DNSContext{
884-
CustomUpstreamConfig: &UpstreamConfig{Upstreams: []upstream.Upstream{&u}},
889+
CustomUpstreamConfig: NewCustomUpstreamConfig(
890+
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
891+
false,
892+
0,
893+
false,
894+
),
895+
Req: createHostTestMessage("host"),
896+
Addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 0}},
897+
}
898+
899+
err = prx.Resolve(&d)
900+
require.NoError(t, err)
901+
902+
assert.Equal(t, ansIP, getIPFromResponse(d.Res))
903+
}
904+
905+
func TestExchangeCustomUpstreamConfigCache(t *testing.T) {
906+
prx := createTestProxy(t, nil)
907+
prx.CacheEnabled = true
908+
prx.initCache()
909+
910+
err := prx.Start()
911+
require.NoError(t, err)
912+
testutil.CleanupAndRequireSuccess(t, prx.Stop)
913+
914+
var count int
915+
916+
ansIP := net.IP{4, 3, 2, 1}
917+
exchangeFunc := func(m *dns.Msg) (resp *dns.Msg, err error) {
918+
resp = &dns.Msg{}
919+
resp.SetReply(m)
920+
resp.Answer = append(resp.Answer, &dns.A{
921+
Hdr: dns.RR_Header{
922+
Name: m.Question[0].Name,
923+
Class: dns.ClassINET,
924+
Rrtype: dns.TypeA,
925+
Ttl: defaultTestTTL,
926+
},
927+
A: ansIP,
928+
})
929+
930+
count++
931+
932+
return resp, nil
933+
}
934+
u := &funcUpstream{
935+
exchangeFunc: exchangeFunc,
936+
}
937+
938+
customUpstreamConfig := NewCustomUpstreamConfig(
939+
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
940+
true,
941+
defaultCacheSize,
942+
prx.EnableEDNSClientSubnet,
943+
)
944+
945+
d := DNSContext{
946+
CustomUpstreamConfig: customUpstreamConfig,
885947
Req: createHostTestMessage("host"),
886948
Addr: &net.TCPAddr{IP: net.IP{1, 2, 3, 0}},
887949
}
888950

889951
err = prx.Resolve(&d)
890952
require.NoError(t, err)
891953

954+
assert.Equal(t, 1, count)
955+
assert.Equal(t, ansIP, getIPFromResponse(d.Res))
956+
957+
err = prx.Resolve(&d)
958+
require.NoError(t, err)
959+
960+
assert.Equal(t, 1, count)
961+
assert.Equal(t, ansIP, getIPFromResponse(d.Res))
962+
963+
customUpstreamConfig.ClearCache()
964+
965+
err = prx.Resolve(&d)
966+
require.NoError(t, err)
967+
968+
assert.Equal(t, 2, count)
892969
assert.Equal(t, ansIP, getIPFromResponse(d.Res))
893970
}
894971

proxy/proxycache.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,34 @@ import (
77
"github.com/AdguardTeam/golibs/netutil"
88
)
99

10-
// replyFromCache tries to get the response from general or subnet cache.
11-
// Returns true on success.
10+
// cacheForContext returns cache object for the given context.
11+
func (p *Proxy) cacheForContext(d *DNSContext) (c *cache) {
12+
if d.CustomUpstreamConfig != nil && d.CustomUpstreamConfig.cache != nil {
13+
return d.CustomUpstreamConfig.cache
14+
}
15+
16+
return p.cache
17+
}
18+
19+
// replyFromCache tries to get the response from general or subnet cache. In
20+
// case the cache is present in d, it's used first. Returns true on success.
1221
func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
22+
dctxCache := p.cacheForContext(d)
23+
1324
var ci *cacheItem
1425
var hitMsg string
1526
var expired bool
1627
var key []byte
1728

29+
// TODO(d.kolyshev): Use EnableEDNSClientSubnet from dctxCache.
1830
if !p.Config.EnableEDNSClientSubnet {
19-
ci, expired, key = p.cache.get(d.Req)
31+
ci, expired, key = dctxCache.get(d.Req)
2032
hitMsg = "serving cached response"
2133
} else if d.ReqECS != nil {
22-
ci, expired, key = p.cache.getWithSubnet(d.Req, d.ReqECS)
34+
ci, expired, key = dctxCache.getWithSubnet(d.Req, d.ReqECS)
2335
hitMsg = "serving response from subnet cache"
2436
} else {
25-
ci, expired, key = p.cache.get(d.Req)
37+
ci, expired, key = dctxCache.get(d.Req)
2638
hitMsg = "serving response from general cache"
2739
}
2840

@@ -35,7 +47,7 @@ func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
3547

3648
log.Debug("dnsproxy: cache: %s", hitMsg)
3749

38-
if p.cache.optimistic && expired {
50+
if dctxCache.optimistic && expired {
3951
// Build a reduced clone of the current context to avoid data race.
4052
minCtxClone := &DNSContext{
4153
// It is only read inside the optimistic resolver.
@@ -53,10 +65,13 @@ func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
5365
return hit
5466
}
5567

56-
// cacheResp stores the response from d in general or subnet cache.
68+
// cacheResp stores the response from d in general or subnet cache. In case the
69+
// cache is present in d, it's used first.
5770
func (p *Proxy) cacheResp(d *DNSContext) {
71+
dctxCache := p.cacheForContext(d)
72+
5873
if !p.EnableEDNSClientSubnet {
59-
p.cache.set(d.Res, d.Upstream)
74+
dctxCache.set(d.Res, d.Upstream)
6075

6176
return
6277
}
@@ -92,13 +107,13 @@ func (p *Proxy) cacheResp(d *DNSContext) {
92107

93108
log.Debug("dnsproxy: cache: ecs option in response: %s", ecs)
94109

95-
p.cache.setWithSubnet(d.Res, d.Upstream, ecs)
110+
dctxCache.setWithSubnet(d.Res, d.Upstream, ecs)
96111
case d.ReqECS != nil:
97112
// Cache the response for all subnets since the server doesn't support
98113
// EDNS Client Subnet option.
99-
p.cache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
114+
dctxCache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
100115
default:
101-
p.cache.set(d.Res, d.Upstream)
116+
dctxCache.set(d.Res, d.Upstream)
102117
}
103118
}
104119

0 commit comments

Comments
 (0)