Skip to content

Commit

Permalink
Pull request: 3978 export ECS fields
Browse files Browse the repository at this point in the history
Merge in DNS/dnsproxy from 3978-ecs-ip to master

Updates AdguardTeam/AdGuardHome#3978

Squashed commit of the following:

commit 6522971
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Feb 16 21:13:12 2022 +0300

    proxy: impl rfc better

commit dd2284a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Feb 16 20:02:48 2022 +0300

    proxy: fix deps, imp tests

commit 50003c4
Merge: d2b98d4 99558ce
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Feb 16 19:24:25 2022 +0300

    Merge branch 'master' into 3978-ecs-ip

commit d2b98d4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Feb 16 19:22:37 2022 +0300

    proxy: turn ecs into ipnet

commit 99ec30d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 15 17:40:27 2022 +0300

    proxy: imp code, docs

commit 7f48aa3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Feb 15 16:15:52 2022 +0300

    proxy: export ecs data
  • Loading branch information
EugeneOne1 committed Feb 17, 2022
1 parent 99558ce commit 1a9ee81
Show file tree
Hide file tree
Showing 16 changed files with 642 additions and 288 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/AdguardTeam/dnsproxy
go 1.17

require (
github.com/AdguardTeam/golibs v0.10.3
github.com/AdguardTeam/golibs v0.10.6
github.com/ameshkov/dnscrypt/v2 v2.2.3
github.com/ameshkov/dnsstamps v1.0.3
github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/golibs v0.10.3 h1:FBgk17zf35ESVWQKIqEUiqqB2bDaCBC8X5vMU760yB4=
github.com/AdguardTeam/golibs v0.10.3/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/AdguardTeam/golibs v0.10.6 h1:6UG6LxWFnG7TfjNzeApw+T68Kqqov0fcDYk9RjhTdhc=
github.com/AdguardTeam/golibs v0.10.6/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
Expand Down
40 changes: 20 additions & 20 deletions proxy/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,15 @@ func (c *cache) get(req *dns.Msg) (ci *cacheItem, expired bool, key []byte) {
return ci, expired, key
}

// getWithSubnet returns cached item for the req if it's found by client's IP
// and a subnet mask. expired is true if the item's TTL is expired. key is the
// resulting key for req. It's returned to avoid recalculating it afterwards.
// getWithSubnet returns cached item for the req if it's found by n. expired is
// true if the item's TTL is expired. k is the resulting key for req. It's
// returned to avoid recalculating it afterwards.
//
// Note that a slow longest-prefix-match algorithm is used, so cache searches
// are performed up to mask+1 times.
func (c *cache) getWithSubnet(req *dns.Msg, cliIP net.IP, mask uint8) (
ci *cacheItem,
expired bool,
key []byte,
) {
func (c *cache) getWithSubnet(req *dns.Msg, n *net.IPNet) (ci *cacheItem, expired bool, k []byte) {
mask, _ := n.Mask.Size()

c.itemsWithSubnetLock.RLock()
defer c.itemsWithSubnetLock.RUnlock()

Expand All @@ -200,18 +198,18 @@ func (c *cache) getWithSubnet(req *dns.Msg, cliIP net.IP, mask uint8) (
var data []byte
for mask++; mask > 0 && data == nil; {
mask--
key = msgToKeyWithSubnet(req, cliIP, mask)
data = c.itemsWithSubnet.Get(key)
k = msgToKeyWithSubnet(req, n.IP, mask)
data = c.itemsWithSubnet.Get(k)
}
if data == nil {
return nil, false, key
return nil, false, k
}

if ci, expired = c.unpackItem(data, req); ci == nil {
c.items.Del(key)
c.itemsWithSubnet.Del(k)
}

return ci, expired, key
return ci, expired, k
}

// initLazy initializes the cache for general requests.
Expand Down Expand Up @@ -267,14 +265,15 @@ func (c *cache) set(ci *cacheItem) {

// setWithSubnet tries to add the ci into cache with subnet and ip used to
// calculate the key.
func (c *cache) setWithSubnet(ci *cacheItem, ip net.IP, mask uint8) {
func (c *cache) setWithSubnet(ci *cacheItem, subnet *net.IPNet) {
if !isCacheable(ci.m) {
return
}

c.initLazyWithSubnet()

key := msgToKeyWithSubnet(ci.m, ip, mask)
pref, _ := subnet.Mask.Size()
key := msgToKeyWithSubnet(ci.m, subnet.IP, pref)
packed := ci.pack()

c.itemsWithSubnetLock.RLock()
Expand Down Expand Up @@ -417,11 +416,12 @@ func msgToKey(m *dns.Msg) (b []byte) {
}

// msgToKeyWithSubnet constructs the cache key from DO bit, type, class, subnet
// mask, client's IP address and question's name of m.
func msgToKeyWithSubnet(m *dns.Msg, clientIP net.IP, mask uint8) (key []byte) {
// mask, client's IP address and question's name of m. ecsIP is expected to be
// masked already.
func msgToKeyWithSubnet(m *dns.Msg, ecsIP net.IP, mask int) (key []byte) {
q := m.Question[0]
cap := 1 + 2*packedMsgLenSz + 1 + len(q.Name)
ipLen := len(clientIP)
ipLen := len(ecsIP)
masked := mask != 0
if masked {
cap += ipLen
Expand All @@ -448,10 +448,10 @@ func msgToKeyWithSubnet(m *dns.Msg, clientIP net.IP, mask uint8) (key []byte) {
k += packedMsgLenSz

// Add mask.
key[k] = mask
key[k] = uint8(mask)
k++
if masked {
k += copy(key[k:], clientIP)
k += copy(key[k:], ecsIP)
}
copy(key[k:], strings.ToLower(q.Name))

Expand Down
34 changes: 25 additions & 9 deletions proxy/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/AdguardTeam/dnsproxy/upstream"
glcache "github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -634,7 +635,10 @@ func TestSubnet(t *testing.T) {
req := (&dns.Msg{}).SetQuestion("example.com.", dns.TypeA)

t.Run("empty", func(t *testing.T) {
ci, expired, key := c.getWithSubnet(req, ip1234, 24)
ci, expired, key := c.getWithSubnet(req, &net.IPNet{
IP: ip1234,
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
})
assert.False(t, expired)
assert.Nil(t, key)
assert.Nil(t, ci)
Expand All @@ -652,10 +656,13 @@ func TestSubnet(t *testing.T) {
Answer: []dns.RR{newRR(t, "example.com. 1 IN A 1.1.1.1")},
}).SetQuestion("example.com.", dns.TypeA)
item.m = resp
c.setWithSubnet(item, ip1234, 16)
c.setWithSubnet(item, &net.IPNet{IP: ip1234, Mask: net.CIDRMask(16, netutil.IPv4BitLen)})

t.Run("different_ip", func(t *testing.T) {
ci, expired, key := c.getWithSubnet(req, ip2234, 24)
ci, expired, key := c.getWithSubnet(req, &net.IPNet{
IP: ip2234,
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
})
assert.False(t, expired)
assert.Equal(t, msgToKeyWithSubnet(req, ip2234, 0), key)

Expand All @@ -670,7 +677,7 @@ func TestSubnet(t *testing.T) {
Answer: []dns.RR{newRR(t, "example.com. 1 IN A 2.2.2.2")},
}).SetQuestion("example.com.", dns.TypeA)
item.m = resp
c.setWithSubnet(item, ip2234, 16)
c.setWithSubnet(item, &net.IPNet{IP: ip2234, Mask: net.CIDRMask(16, netutil.IPv4BitLen)})

// Add a response entry without subnet.
resp = (&dns.Msg{
Expand All @@ -680,10 +687,13 @@ func TestSubnet(t *testing.T) {
Answer: []dns.RR{newRR(t, "example.com. 1 IN A 3.3.3.3")},
}).SetQuestion("example.com.", dns.TypeA)
item.m = resp
c.setWithSubnet(item, net.IP{}, 0)
c.setWithSubnet(item, &net.IPNet{IP: nil, Mask: nil})

t.Run("with_subnet_1", func(t *testing.T) {
ci, expired, key := c.getWithSubnet(req, ip1234, 24)
ci, expired, key := c.getWithSubnet(req, &net.IPNet{
IP: ip1234,
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
})
assert.False(t, expired)
assert.Equal(t, msgToKeyWithSubnet(req, ip1234, 16), key)

Expand All @@ -698,7 +708,10 @@ func TestSubnet(t *testing.T) {
})

t.Run("with_subnet_2", func(t *testing.T) {
ci, expired, key := c.getWithSubnet(req, ip2234, 24)
ci, expired, key := c.getWithSubnet(req, &net.IPNet{
IP: ip2234,
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
})
assert.False(t, expired)
assert.Equal(t, msgToKeyWithSubnet(req, ip2234, 16), key)

Expand All @@ -713,9 +726,12 @@ func TestSubnet(t *testing.T) {
})

t.Run("with_subnet_3", func(t *testing.T) {
ci, expired, key := c.getWithSubnet(req, ip3234, 24)
ci, expired, key := c.getWithSubnet(req, &net.IPNet{
IP: ip3234,
Mask: net.CIDRMask(24, netutil.IPv4BitLen),
})
assert.False(t, expired)
assert.Equal(t, msgToKeyWithSubnet(req, ip3234, 0), key)
assert.Equal(t, msgToKeyWithSubnet(req, ip1234, 0), key)

require.NotNil(t, ci)
require.NotNil(t, ci.m)
Expand Down
33 changes: 17 additions & 16 deletions proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,26 @@ type Config struct {
// Similar to dnsmasq's "bogus-nxdomain"
BogusNXDomain []*net.IPNet

// Enable EDNS Client Subnet option
// DNS requests to the upstream server will contain an OPT record with Client Subnet option.
// If the original request already has this option set, we pass it through as is.
// Otherwise, we set it ourselves using the client IP with subnet /24 (for IPv4) and /112 (for IPv6).
// Enable EDNS Client Subnet option DNS requests to the upstream server will
// contain an OPT record with Client Subnet option. If the original request
// already has this option set, we pass it through as is. Otherwise, we set
// it ourselves using the client IP with subnet /24 (for IPv4) and /56 (for
// IPv6).
//
// If the upstream server supports ECS, it sets subnet number in the response.
// This subnet number along with the client IP and other data is used as a cache key.
// Next time, if a client from the same subnet requests this host name,
// we get the response from cache.
// If another client from a different subnet requests this host name,
// we pass his request to the upstream server.
// If the upstream server supports ECS, it sets subnet number in the
// response. This subnet number along with the client IP and other data is
// used as a cache key. Next time, if a client from the same subnet
// requests this host name, we get the response from cache. If another
// client from a different subnet requests this host name, we pass his
// request to the upstream server.
//
// If the upstream server doesn't support ECS (there's no subnet number in response),
// this response will be cached for all clients.
// If the upstream server doesn't support ECS (there's no subnet number in
// response), this response will be cached for all clients.
//
// If client IP is private (i.e. not public), we don't add EDNS record into a request.
// And so there will be no EDNS record in response either.
// We store these responses in general cache (without subnet)
// so they will never be used for clients with public IP addresses.
// If client IP is private (i.e. not public), we don't add EDNS record into
// a request. And so there will be no EDNS record in response either. We
// store these responses in general cache (without subnet) so they will
// never be used for clients with public IP addresses.
EnableEDNSClientSubnet bool
EDNSAddr net.IP // ECS IP used in request

Expand Down
8 changes: 4 additions & 4 deletions proxy/dns64.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ func (p *Proxy) isEmptyAAAAResponse(resp, req *dns.Msg) bool {
req.Question[0].Qtype == dns.TypeAAAA
}

// isNAT64PrefixAvailable returns true if NAT64 prefix was calculated
// isNAT64PrefixAvailable returns true if NAT64 prefix was calculated.
func (p *Proxy) isNAT64PrefixAvailable() bool {
p.nat64PrefixLock.Lock()
prefixSize := len(p.nat64Prefix)
p.nat64PrefixLock.Unlock()
return prefixSize == NAT64PrefixLength
defer p.nat64PrefixLock.Unlock()

return len(p.nat64Prefix) == NAT64PrefixLength
}

// SetNAT64Prefix sets NAT64 prefix
Expand Down
36 changes: 17 additions & 19 deletions proxy/dns_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,8 @@ type DNSContext struct {
// instance.
RequestID uint64

// ecsReqIP is the ECS IP used in the request.
ecsReqIP net.IP
// ecsReqMask is the length of ECS mask used in the request.
ecsReqMask uint8
// ReqECS is the EDNS Client Subnet used in the request.
ReqECS *net.IPNet

// adBit is the authenticated data flag from the request.
adBit bool
Expand All @@ -79,39 +77,39 @@ type DNSContext struct {
}

// calcFlagsAndSize lazily calculates some values required for Resolve method.
func (ctx *DNSContext) calcFlagsAndSize() {
if ctx.udpSize != 0 || ctx.Req == nil {
func (dctx *DNSContext) calcFlagsAndSize() {
if dctx.udpSize != 0 || dctx.Req == nil {
return
}

ctx.adBit = ctx.Req.AuthenticatedData
ctx.udpSize = defaultUDPBufSize
if o := ctx.Req.IsEdns0(); o != nil {
ctx.hasEDNS0 = true
ctx.doBit = o.Do()
ctx.udpSize = o.UDPSize()
dctx.adBit = dctx.Req.AuthenticatedData
dctx.udpSize = defaultUDPBufSize
if o := dctx.Req.IsEdns0(); o != nil {
dctx.hasEDNS0 = true
dctx.doBit = o.Do()
dctx.udpSize = o.UDPSize()
}
}

// scrub prepares the d.Res to be written. Truncation is applied as well if
// necessary.
func (ctx *DNSContext) scrub() {
if ctx.Res == nil || ctx.Req == nil {
func (dctx *DNSContext) scrub() {
if dctx.Res == nil || dctx.Req == nil {
return
}

// We should guarantee that all the values we need are calculated.
ctx.calcFlagsAndSize()
dctx.calcFlagsAndSize()

// RFC-6891 (https://tools.ietf.org/html/rfc6891) states that response
// mustn't contain an EDNS0 RR if the request doesn't include it.
//
// See https://github.com/AdguardTeam/dnsproxy/issues/132.
if ctx.hasEDNS0 && ctx.Res.IsEdns0() == nil {
ctx.Res.SetEdns0(ctx.udpSize, ctx.doBit)
if dctx.hasEDNS0 && dctx.Res.IsEdns0() == nil {
dctx.Res.SetEdns0(dctx.udpSize, dctx.doBit)
}

ctx.Res.Truncate(proxyutil.DNSSize(ctx.Proto == ProtoUDP, ctx.Req))
dctx.Res.Truncate(proxyutil.DNSSize(dctx.Proto == ProtoUDP, dctx.Req))
// Some devices require DNS message compression.
ctx.Res.Compress = true
dctx.Res.Compress = true
}
Loading

0 comments on commit 1a9ee81

Please sign in to comment.