Skip to content

Commit

Permalink
proxy: export ecs data
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Feb 15, 2022
1 parent 5956b6d commit 7f48aa3
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 111 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.5
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
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
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.5 h1:4/nl1yIBJOv5luVu9SURW8LfgOjI3zQ2moIUy/1k0y4=
github.com/AdguardTeam/golibs v0.10.5/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
9 changes: 5 additions & 4 deletions proxy/dns_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ 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
// ECSReqIP is the EDNS Client-Subnet IP address used in the request.
ECSReqIP net.IP
// ECSReqMask is the length of the EDNS Client-Subnet mask used in the
// request.
ECSReqMask uint8

// adBit is the authenticated data flag from the request.
adBit bool
Expand Down
85 changes: 35 additions & 50 deletions proxy/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net"

"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)

Expand Down Expand Up @@ -67,7 +68,7 @@ func genSOA(request *dns.Msg, retry uint32) []dns.RR {
}

// parseECS parses the EDNS client subnet option from m.
func parseECS(m *dns.Msg) (addr net.IP, mask, scope uint8) {
func parseECS(m *dns.Msg) (ip net.IP, mask, scope uint8) {
opt := m.IsEdns0()
if opt == nil {
return nil, 0, 0
Expand Down Expand Up @@ -100,7 +101,7 @@ func setECS(m *dns.Msg, ip net.IP, scope uint8) (net.IP, uint8) {
// in EDNS client subnet option.
defaultECSv4 = 24
// defaultECSv6 is the default length of network mask for IPv6 address
// in EDNS client subnet option. The size of 7 bytes is chosen as a
// in EDNS client subnet option. The size of 7 octets is chosen as a
// reasonable minimum since at least Google's public DNS refuses
// requests containing the options with longer network masks.
defaultECSv6 = 56
Expand All @@ -113,12 +114,12 @@ func setECS(m *dns.Msg, ip net.IP, scope uint8) (net.IP, uint8) {
if ip4 := ip.To4(); ip4 != nil {
e.Family = 1
e.SourceNetmask = defaultECSv4
e.Address = ip4.Mask(net.CIDRMask(defaultECSv4, net.IPv4len*8))
e.Address = ip4.Mask(net.CIDRMask(defaultECSv4, netutil.IPv4BitLen))
} else {
// Assume the IP address has already been validated.
e.Family = 2
e.SourceNetmask = defaultECSv6
e.Address = ip.Mask(net.CIDRMask(defaultECSv6, net.IPv6len*8))
e.Address = ip.Mask(net.CIDRMask(defaultECSv6, netutil.IPv6BitLen))
}

// If OPT record already exists so just add EDNS option inside it. Note
Expand All @@ -145,54 +146,38 @@ func setECS(m *dns.Msg, ip net.IP, scope uint8) (net.IP, uint8) {

// Return TRUE if IP is within public Internet IP range
// nolint (gocyclo)
func isPublicIP(ip net.IP) bool {
ip4 := ip.To4()
if ip4 != nil {
switch ip4[0] {
func isPublicIP(ip net.IP) (ok bool) {
if ip = ip.To4(); ip == nil {
return !ip.IsLoopback() && !ip.IsLinkLocalMulticast() && !ip.IsLinkLocalUnicast()
}

switch ip[0] {
case 0, 10, 127:
return false
case 169:
return ip[1] != 254
case 172:
return ip[1] < 16 || ip[1] > 31
case 192:
switch ip[1] {
case 0:
return false // software
case 10:
return false // private network
case 127:
return false // loopback
case 169:
if ip4[1] == 254 {
return false // link-local
}
case 172:
if ip4[1] >= 16 && ip4[1] <= 31 {
return false // private network
}
case 192:
if (ip4[1] == 0 && ip4[2] == 0) || // private network
(ip4[1] == 0 && ip4[2] == 2) || // documentation
(ip4[1] == 88 && ip4[2] == 99) || // reserved
(ip4[1] == 168) { // private network
return false
}
case 198:
if (ip4[1] == 18 || ip4[2] == 19) || // private network
(ip4[1] == 51 || ip4[2] == 100) { // documentation
return false
}
case 203:
if ip4[1] == 0 && ip4[2] == 113 { // documentation
return false
}
case 224:
if ip4[1] == 0 && ip4[2] == 0 { // multicast
return false
}
case 255:
if ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255 { // subnet
return false
}
}
} else {
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
return ip[2] != 0 && ip[2] != 2
case 88:
return ip[2] != 99
case 168:
return false
default:
return true
}
case 198:
return (ip[1] != 18 && ip[2] != 19) && (ip[1] != 51 && ip[2] != 100)
case 203:
return ip[1] != 0 || ip[2] != 113
case 224:
return ip[1] != 0 || ip[2] != 0
case 255:
return ip[1] != 255 || ip[2] != 255 || ip[3] != 255
default:
return true
}

return true
}
45 changes: 22 additions & 23 deletions proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ const defaultUDPBufSize = 2048
// Resolve is the default resolving method used by the DNS proxy to query
// upstream servers.
func (p *Proxy) Resolve(d *DNSContext) (err error) {
if p.Config.EnableEDNSClientSubnet {
p.processECS(d)
if p.EnableEDNSClientSubnet {
d.processECS(p.EDNSAddr)
}

d.calcFlagsAndSize()
Expand Down Expand Up @@ -506,31 +506,30 @@ func (p *Proxy) Resolve(d *DNSContext) (err error) {
return err
}

// Set EDNS Client-Subnet data in DNS request
func (p *Proxy) processECS(d *DNSContext) {
d.ecsReqIP = nil
d.ecsReqMask = uint8(0)

ip, mask, _ := parseECS(d.Req)
if mask == 0 {
// Set EDNS Client-Subnet data
var clientIP net.IP
if p.Config.EDNSAddr != nil {
clientIP = p.Config.EDNSAddr
} else {
clientIP, _ = netutil.IPAndPortFromAddr(d.Addr)
}
// processECS adds EDNS Client-Subnet data into the request from d.
func (d *DNSContext) processECS(cliIP net.IP) {
d.ECSReqIP, d.ECSReqMask, _ = parseECS(d.Req)
if d.ECSReqMask != 0 {
log.Debug("passing through ecs: %s/%d", d.ECSReqIP, d.ECSReqMask)

return
}

if clientIP != nil && isPublicIP(clientIP) {
ip, mask = setECS(d.Req, clientIP, 0)
log.Debug("Set ECS data: %s/%d", ip, mask)
// Set ECS.
d.ECSReqIP = nil

if cliIP == nil {
cliIP, _ = netutil.IPAndPortFromAddr(d.Addr)
if cliIP == nil {
return
}
} else {
log.Debug("Passing through ECS data: %s/%d", ip, mask)
}

d.ecsReqIP = ip
d.ecsReqMask = mask
if isPublicIP(cliIP) {
d.ECSReqIP, d.ECSReqMask = setECS(d.Req, cliIP, 0)

log.Debug("setting ecs: %s/%d", d.ECSReqIP, d.ECSReqMask)
}
}

// newDNSContext returns a new properly initialized *DNSContext.
Expand Down
18 changes: 9 additions & 9 deletions proxy/proxy_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
var key []byte
if !p.Config.EnableEDNSClientSubnet {
ci, expired, key = p.cache.get(d.Req)
} else if d.ecsReqMask != 0 {
ci, expired, key = p.cache.getWithSubnet(d.Req, d.ecsReqIP, d.ecsReqMask)
} else if d.ECSReqMask != 0 {
ci, expired, key = p.cache.getWithSubnet(d.Req, d.ECSReqIP, d.ECSReqMask)
hitMsg = "serving response from subnet cache"
} else {
ci, expired, key = p.cache.get(d.Req)
Expand All @@ -37,10 +37,10 @@ func (p *Proxy) replyFromCache(d *DNSContext) (hit bool) {
minCtxClone := &DNSContext{
// It is only read inside the optimistic resolver.
CustomUpstreamConfig: d.CustomUpstreamConfig,
ecsReqMask: d.ecsReqMask,
ECSReqMask: d.ECSReqMask,
}
if ecsReqIP := d.ecsReqIP; ecsReqIP != nil {
minCtxClone.ecsReqIP = netutil.CloneIP(ecsReqIP)
if ecsReqIP := d.ECSReqIP; ecsReqIP != nil {
minCtxClone.ECSReqIP = netutil.CloneIP(ecsReqIP)
}
if d.Req != nil {
req := d.Req.Copy()
Expand All @@ -65,22 +65,22 @@ func (p *Proxy) cacheResp(d *DNSContext) {
m: res,
u: upsAddr,
}
if !p.Config.EnableEDNSClientSubnet {
if !p.EnableEDNSClientSubnet {
p.cache.set(item)

return
}

ip, mask, scope := parseECS(res)
if ip != nil {
if ip.Equal(d.ecsReqIP) && mask == d.ecsReqMask {
if ip.Equal(d.ECSReqIP) && mask == d.ECSReqMask {
log.Debug("ECS option in response: %s/%d", ip, scope)
p.cache.setWithSubnet(item, ip, scope)
} else {
log.Debug("Invalid response from server: ECS data mismatch: %s/%d -- %s/%d",
d.ecsReqIP, d.ecsReqMask, ip, mask)
d.ECSReqIP, d.ECSReqMask, ip, mask)
}
} else if d.ecsReqIP != nil {
} else if d.ECSReqIP != nil {
// server doesn't support ECS - cache response for all subnets
p.cache.setWithSubnet(item, ip, scope)
} else {
Expand Down
Loading

0 comments on commit 7f48aa3

Please sign in to comment.