Skip to content

Commit

Permalink
Merge branch 'master' into AG-27616-ratelimit-whitelist-netip-addr
Browse files Browse the repository at this point in the history
  • Loading branch information
schzhn committed Nov 28, 2023
2 parents 8cc3b79 + 280cca3 commit 6b8f86f
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 65 deletions.
57 changes: 54 additions & 3 deletions proxy/dnscontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ type DNSContext struct {
// ReqECS is the EDNS Client Subnet used in the request.
ReqECS *net.IPNet

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

// Req is the request message.
Req *dns.Msg
Expand Down Expand Up @@ -156,3 +157,53 @@ const (
// DoQv1 represents DoQ v1.0: https://www.rfc-editor.org/rfc/rfc9250.html.
DoQv1 DoQVersion = 0x01
)

// CustomUpstreamConfig contains upstreams configuration with an optional cache.
type CustomUpstreamConfig struct {
// upstream is the upstream configuration.
upstream *UpstreamConfig

// cache is an optional cache for upstreams in the current configuration.
// It is disabled if nil.
//
// TODO(d.kolyshev): Move this cache to [UpstreamConfig].
cache *cache
}

// NewCustomUpstreamConfig returns new custom upstream configuration.
func NewCustomUpstreamConfig(
u *UpstreamConfig,
cacheEnabled bool,
cacheSize int,
enableEDNSClientSubnet bool,
) (c *CustomUpstreamConfig) {
var customCache *cache
if cacheEnabled {
// TODO(d.kolyshev): Support optimistic with newOptimisticResolver.
customCache = newCache(cacheSize, enableEDNSClientSubnet, false)
}

return &CustomUpstreamConfig{
upstream: u,
cache: customCache,
}
}

// Close closes the custom upstream config.
func (c *CustomUpstreamConfig) Close() (err error) {
if c.upstream == nil {
return nil
}

return c.upstream.Close()
}

// ClearCache removes all items from the cache.
func (c *CustomUpstreamConfig) ClearCache() {
if c.cache == nil {
return
}

c.cache.clearItems()
c.cache.clearItemsWithSubnet()
}
12 changes: 8 additions & 4 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ type Proxy struct {
// --

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

// shortFlighter is used to resolve the expired cached requests without
Expand Down Expand Up @@ -499,7 +501,7 @@ func (p *Proxy) selectUpstreams(d *DNSContext) (upstreams []upstream.Upstream) {

if custom := d.CustomUpstreamConfig; custom != nil {
// Try to use custom.
upstreams = getUpstreams(custom, host)
upstreams = getUpstreams(custom.upstream, host)
if len(upstreams) > 0 {
return upstreams
}
Expand Down Expand Up @@ -626,7 +628,6 @@ func (p *Proxy) Resolve(dctx *DNSContext) (err error) {

dctx.calcFlagsAndSize()

// Use cache only if it's enabled and the query doesn't use custom upstream.
// Also don't lookup the cache for responses with DNSSEC checking disabled
// since only validated responses are cached and those may be not the
// desired result for user specifying CD flag.
Expand Down Expand Up @@ -680,9 +681,12 @@ func (p *Proxy) cacheWorks(dctx *DNSContext) (ok bool) {
switch {
case p.cache == nil:
reason = "disabled"
case dctx.CustomUpstreamConfig != nil:
case dctx.CustomUpstreamConfig != nil && dctx.CustomUpstreamConfig.cache == nil:
// In case of custom upstream cache is not configured, the global proxy
// cache cannot be used because different upstreams can return different
// results.
// See https://github.com/AdguardTeam/dnsproxy/issues/169.
reason = "custom upstreams used"
reason = "custom upstreams cache is not configured"
case dctx.Req.CheckingDisabled:
reason = "dnssec check disabled"
default:
Expand Down
87 changes: 82 additions & 5 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,14 @@ func TestProxy_ReplyFromUpstream_badResponse(t *testing.T) {
}

d := &DNSContext{
CustomUpstreamConfig: &UpstreamConfig{Upstreams: []upstream.Upstream{u}},
Req: createHostTestMessage("host"),
Addr: netip.MustParseAddrPort("1.2.3.0:1234"),
CustomUpstreamConfig: NewCustomUpstreamConfig(
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
false,
0,
false,
),
Req: createHostTestMessage("host"),
Addr: netip.MustParseAddrPort("1.2.3.0:1234"),
}

var err error
Expand All @@ -867,7 +872,7 @@ func TestExchangeCustomUpstreamConfig(t *testing.T) {
testutil.CleanupAndRequireSuccess(t, prx.Stop)

ansIP := net.IP{4, 3, 2, 1}
u := testUpstream{
u := &testUpstream{
ans: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Rrtype: dns.TypeA,
Expand All @@ -879,14 +884,86 @@ func TestExchangeCustomUpstreamConfig(t *testing.T) {
}

d := DNSContext{
CustomUpstreamConfig: &UpstreamConfig{Upstreams: []upstream.Upstream{&u}},
CustomUpstreamConfig: NewCustomUpstreamConfig(
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
false,
0,
false,
),
Req: createHostTestMessage("host"),
Addr: netip.MustParseAddrPort("1.2.3.0:1234"),
}

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

assert.Equal(t, ansIP, getIPFromResponse(d.Res))
}

func TestExchangeCustomUpstreamConfigCache(t *testing.T) {
prx := createTestProxy(t, nil)
prx.CacheEnabled = true
prx.initCache()

err := prx.Start()
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, prx.Stop)

var count int

ansIP := net.IP{4, 3, 2, 1}
exchangeFunc := func(m *dns.Msg) (resp *dns.Msg, err error) {
resp = &dns.Msg{}
resp.SetReply(m)
resp.Answer = append(resp.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: m.Question[0].Name,
Class: dns.ClassINET,
Rrtype: dns.TypeA,
Ttl: defaultTestTTL,
},
A: ansIP,
})

count++

return resp, nil
}
u := &funcUpstream{
exchangeFunc: exchangeFunc,
}

customUpstreamConfig := NewCustomUpstreamConfig(
&UpstreamConfig{Upstreams: []upstream.Upstream{u}},
true,
defaultCacheSize,
prx.EnableEDNSClientSubnet,
)

d := DNSContext{
CustomUpstreamConfig: customUpstreamConfig,
Req: createHostTestMessage("host"),
Addr: netip.MustParseAddrPort("1.2.3.0:1234"),
}

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

assert.Equal(t, 1, count)
assert.Equal(t, ansIP, getIPFromResponse(d.Res))

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

assert.Equal(t, 1, count)
assert.Equal(t, ansIP, getIPFromResponse(d.Res))

customUpstreamConfig.ClearCache()

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

assert.Equal(t, 2, count)
assert.Equal(t, ansIP, getIPFromResponse(d.Res))
}

Expand Down
37 changes: 26 additions & 11 deletions proxy/proxycache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,34 @@ import (
"github.com/AdguardTeam/golibs/netutil"
)

// replyFromCache tries to get the response from general or subnet cache.
// Returns true on success.
// 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 = p.cache.get(d.Req)
ci, expired, key = dctxCache.get(d.Req)
hitMsg = "serving cached response"
} else if d.ReqECS != nil {
ci, expired, key = p.cache.getWithSubnet(d.Req, d.ReqECS)
ci, expired, key = dctxCache.getWithSubnet(d.Req, d.ReqECS)
hitMsg = "serving response from subnet cache"
} else {
ci, expired, key = p.cache.get(d.Req)
ci, expired, key = dctxCache.get(d.Req)
hitMsg = "serving response from general cache"
}

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

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

if p.cache.optimistic && expired {
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.
Expand All @@ -53,10 +65,13 @@ func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
return hit
}

// cacheResp stores the response from d in general or subnet cache.
// 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 {
p.cache.set(d.Res, d.Upstream)
dctxCache.set(d.Res, d.Upstream)

return
}
Expand Down Expand Up @@ -92,13 +107,13 @@ func (p *Proxy) cacheResp(d *DNSContext) {

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

p.cache.setWithSubnet(d.Res, d.Upstream, 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.
p.cache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
dctxCache.setWithSubnet(d.Res, d.Upstream, &net.IPNet{IP: nil, Mask: nil})
default:
p.cache.set(d.Res, d.Upstream)
dctxCache.set(d.Res, d.Upstream)
}
}

Expand Down
Loading

0 comments on commit 6b8f86f

Please sign in to comment.