Pull request: 2889 internal hosts restriction
Merge in DNS/adguard-home from 2889-imp-autohosts to master
Closes #2889.
Squashed commit of the following:
commit 1d3b649364f991c092851c02d99827769cce8b70
Merge: abc6e1c8 1a214eaa
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Thu Apr 8 17:59:51 2021 +0300
Merge branch 'master' into 2889-imp-autohosts
commit abc6e1c8830e41a774c6d239ddd7b722b29df93e
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Thu Apr 8 17:34:56 2021 +0300
dnsforward: imp code
commit 4b2b9140d3e2526a935216ba42faba9b86b9ef3f
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Thu Apr 8 17:31:34 2021 +0300
dnsforward: respond with nxdomain
commit 814667417a1b02c152a034852858b96412874f85
Author: Eugene Burkov <e.burkov@adguard.com>
Date: Tue Apr 6 19:16:14 2021 +0300
dnsforward: restrict the access to intl hosts for ext clients
This commit is contained in:
parent
1a214eaabc
commit
7afc692632
|
@ -28,6 +28,8 @@ and this project adheres to
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- The access to the private hosts is now forbidden for users from external
|
||||||
|
networks ([#2889]).
|
||||||
- The reverse lookup for local addresses is now performed via local resolvers
|
- The reverse lookup for local addresses is now performed via local resolvers
|
||||||
([#2704]).
|
([#2704]).
|
||||||
- Stricter validation of the IP addresses of static leases in the DHCP server
|
- Stricter validation of the IP addresses of static leases in the DHCP server
|
||||||
|
@ -64,6 +66,7 @@ and this project adheres to
|
||||||
[#2828]: https://github.com/AdguardTeam/AdGuardHome/issues/2828
|
[#2828]: https://github.com/AdguardTeam/AdGuardHome/issues/2828
|
||||||
[#2835]: https://github.com/AdguardTeam/AdGuardHome/issues/2835
|
[#2835]: https://github.com/AdguardTeam/AdGuardHome/issues/2835
|
||||||
[#2838]: https://github.com/AdguardTeam/AdGuardHome/issues/2838
|
[#2838]: https://github.com/AdguardTeam/AdGuardHome/issues/2838
|
||||||
|
[#2889]: https://github.com/AdguardTeam/AdGuardHome/issues/2889
|
||||||
[#2927]: https://github.com/AdguardTeam/AdGuardHome/issues/2927
|
[#2927]: https://github.com/AdguardTeam/AdGuardHome/issues/2927
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,9 @@ type dnsContext struct {
|
||||||
// origReqDNSSEC shows if the DNSSEC flag in the original request from
|
// origReqDNSSEC shows if the DNSSEC flag in the original request from
|
||||||
// the client is set.
|
// the client is set.
|
||||||
origReqDNSSEC bool
|
origReqDNSSEC bool
|
||||||
|
// isLocalClient shows if client's IP address is from locally-served
|
||||||
|
// network.
|
||||||
|
isLocalClient bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// resultCode is the result of a request processing function.
|
// resultCode is the result of a request processing function.
|
||||||
|
@ -81,6 +84,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||||
// appropriate handler.
|
// appropriate handler.
|
||||||
mods := []modProcessFunc{
|
mods := []modProcessFunc{
|
||||||
processInitial,
|
processInitial,
|
||||||
|
s.processDetermineLocal,
|
||||||
s.processInternalHosts,
|
s.processInternalHosts,
|
||||||
s.processRestrictLocal,
|
s.processRestrictLocal,
|
||||||
s.processInternalIPAddrs,
|
s.processInternalIPAddrs,
|
||||||
|
@ -191,6 +195,21 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||||
s.tablePTRLock.Unlock()
|
s.tablePTRLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processDetermineLocal determines if the client's IP address is from
|
||||||
|
// locally-served network and saves the result into the context.
|
||||||
|
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||||
|
rc = resultCodeSuccess
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
if ip = IPFromAddr(dctx.proxyCtx.Addr); ip == nil {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
dctx.isLocalClient = s.subnetDetector.IsLocallyServedNetwork(ip)
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
// hostToIP tries to get an IP leased by DHCP and returns the copy of address
|
// hostToIP tries to get an IP leased by DHCP and returns the copy of address
|
||||||
// since the data inside the internal table may be changed while request
|
// since the data inside the internal table may be changed while request
|
||||||
// processing. It's safe for concurrent use.
|
// processing. It's safe for concurrent use.
|
||||||
|
@ -235,11 +254,22 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(e.burkov): Restrict the access for external clients.
|
d := dctx.proxyCtx
|
||||||
|
if !dctx.isLocalClient {
|
||||||
|
log.Debug("dns: %q requests for internal host", d.Addr)
|
||||||
|
d.Res = s.genNXDomain(req)
|
||||||
|
|
||||||
|
// Do not even put into query log.
|
||||||
|
return resultCodeFinish
|
||||||
|
}
|
||||||
|
|
||||||
ip, ok := s.hostToIP(host)
|
ip, ok := s.hostToIP(host)
|
||||||
if !ok {
|
if !ok {
|
||||||
return resultCodeSuccess
|
// TODO(e.burkov): Inspect special cases when user want to apply
|
||||||
|
// some rules handled by other processors to the hosts with TLD.
|
||||||
|
d.Res = s.genNXDomain(req)
|
||||||
|
|
||||||
|
return resultCodeFinish
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("dns: internal record: %s -> %s", q.Name, ip)
|
log.Debug("dns: internal record: %s -> %s", q.Name, ip)
|
||||||
|
@ -257,8 +287,8 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
|
||||||
return resultCodeSuccess
|
return resultCodeSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRestrictLocal responds with empty answers to PTR requests for IP
|
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
|
||||||
// addresses in locally-served network from external clients.
|
// in locally-served network from external clients.
|
||||||
func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||||
d := ctx.proxyCtx
|
d := ctx.proxyCtx
|
||||||
req := d.Req
|
req := d.Req
|
||||||
|
@ -280,10 +310,9 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
|
||||||
// assume that all the DHCP leases we give are locally-served or at
|
// assume that all the DHCP leases we give are locally-served or at
|
||||||
// least don't need to be unaccessable externally.
|
// least don't need to be unaccessable externally.
|
||||||
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
if s.subnetDetector.IsLocallyServedNetwork(ip) {
|
||||||
clientIP := IPFromAddr(d.Addr)
|
if !ctx.isLocalClient {
|
||||||
if !s.subnetDetector.IsLocallyServedNetwork(clientIP) {
|
log.Debug("dns: %q requests for internal ip", d.Addr)
|
||||||
log.Debug("dns: %q requests for internal ip", clientIP)
|
d.Res = s.genNXDomain(req)
|
||||||
d.Res = s.makeResponse(req)
|
|
||||||
|
|
||||||
// Do not even put into query log.
|
// Do not even put into query log.
|
||||||
return resultCodeFinish
|
return resultCodeFinish
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||||
|
@ -13,56 +14,188 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_ProcessInternalHosts(t *testing.T) {
|
func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||||
|
snd, err := aghnet.NewSubnetDetector()
|
||||||
|
require.NoError(t, err)
|
||||||
|
s := &Server{
|
||||||
|
subnetDetector: snd,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
cliIP net.IP
|
||||||
|
want bool
|
||||||
|
}{{
|
||||||
|
name: "local",
|
||||||
|
cliIP: net.IP{192, 168, 0, 1},
|
||||||
|
want: true,
|
||||||
|
}, {
|
||||||
|
name: "external",
|
||||||
|
cliIP: net.IP{250, 249, 0, 1},
|
||||||
|
want: false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
proxyCtx := &proxy.DNSContext{
|
||||||
|
Addr: &net.TCPAddr{
|
||||||
|
IP: tc.cliIP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dctx := &dnsContext{
|
||||||
|
proxyCtx: proxyCtx,
|
||||||
|
}
|
||||||
|
s.processDetermineLocal(dctx)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.want, dctx.isLocalClient)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_ProcessInternalHosts_localRestriction(t *testing.T) {
|
||||||
knownIP := net.IP{1, 2, 3, 4}
|
knownIP := net.IP{1, 2, 3, 4}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
host string
|
host string
|
||||||
suffix string
|
|
||||||
wantErrMsg string
|
|
||||||
wantIP net.IP
|
wantIP net.IP
|
||||||
qtyp uint16
|
|
||||||
wantRes resultCode
|
wantRes resultCode
|
||||||
|
isLocalCli bool
|
||||||
}{{
|
}{{
|
||||||
name: "success_external",
|
name: "local_client_success",
|
||||||
host: "example.com",
|
|
||||||
suffix: defaultAutohostSuffix,
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantIP: nil,
|
|
||||||
qtyp: dns.TypeA,
|
|
||||||
wantRes: resultCodeSuccess,
|
|
||||||
}, {
|
|
||||||
name: "success_external_non_a",
|
|
||||||
host: "example.com",
|
|
||||||
suffix: defaultAutohostSuffix,
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantIP: nil,
|
|
||||||
qtyp: dns.TypeCNAME,
|
|
||||||
wantRes: resultCodeSuccess,
|
|
||||||
}, {
|
|
||||||
name: "success_internal",
|
|
||||||
host: "example.lan",
|
host: "example.lan",
|
||||||
suffix: defaultAutohostSuffix,
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantIP: knownIP,
|
wantIP: knownIP,
|
||||||
qtyp: dns.TypeA,
|
|
||||||
wantRes: resultCodeSuccess,
|
wantRes: resultCodeSuccess,
|
||||||
|
isLocalCli: true,
|
||||||
}, {
|
}, {
|
||||||
name: "success_internal_unknown",
|
name: "local_client_unknown_host",
|
||||||
host: "example-new.lan",
|
host: "wronghost.lan",
|
||||||
suffix: defaultAutohostSuffix,
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantIP: nil,
|
wantIP: nil,
|
||||||
qtyp: dns.TypeA,
|
wantRes: resultCodeFinish,
|
||||||
wantRes: resultCodeSuccess,
|
isLocalCli: true,
|
||||||
}, {
|
}, {
|
||||||
name: "success_internal_aaaa",
|
name: "external_client_known_host",
|
||||||
host: "example.lan",
|
host: "example.lan",
|
||||||
suffix: defaultAutohostSuffix,
|
|
||||||
wantErrMsg: "",
|
|
||||||
wantIP: nil,
|
wantIP: nil,
|
||||||
qtyp: dns.TypeAAAA,
|
wantRes: resultCodeFinish,
|
||||||
wantRes: resultCodeSuccess,
|
isLocalCli: false,
|
||||||
|
}, {
|
||||||
|
name: "external_client_unknown_host",
|
||||||
|
host: "wronghost.lan",
|
||||||
|
wantIP: nil,
|
||||||
|
wantRes: resultCodeFinish,
|
||||||
|
isLocalCli: false,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
s := &Server{
|
||||||
|
autohostSuffix: defaultAutohostSuffix,
|
||||||
|
tableHostToIP: map[string]net.IP{
|
||||||
|
"example": knownIP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &dns.Msg{
|
||||||
|
MsgHdr: dns.MsgHdr{
|
||||||
|
Id: dns.Id(),
|
||||||
|
},
|
||||||
|
Question: []dns.Question{{
|
||||||
|
Name: dns.Fqdn(tc.host),
|
||||||
|
Qtype: dns.TypeA,
|
||||||
|
Qclass: dns.ClassINET,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
dctx := &dnsContext{
|
||||||
|
proxyCtx: &proxy.DNSContext{
|
||||||
|
Req: req,
|
||||||
|
},
|
||||||
|
isLocalClient: tc.isLocalCli,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := s.processInternalHosts(dctx)
|
||||||
|
require.Equal(t, tc.wantRes, res)
|
||||||
|
pctx := dctx.proxyCtx
|
||||||
|
if tc.wantRes == resultCodeFinish {
|
||||||
|
require.NotNil(t, pctx.Res)
|
||||||
|
|
||||||
|
assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode)
|
||||||
|
assert.Len(t, pctx.Res.Answer, 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.wantIP == nil {
|
||||||
|
assert.Nil(t, pctx.Res)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, pctx.Res)
|
||||||
|
|
||||||
|
ans := pctx.Res.Answer
|
||||||
|
require.Len(t, ans, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.wantIP, ans[0].(*dns.A).A)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||||
|
const (
|
||||||
|
examplecom = "example.com"
|
||||||
|
examplelan = "example.lan"
|
||||||
|
)
|
||||||
|
|
||||||
|
knownIP := net.IP{1, 2, 3, 4}
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
host string
|
||||||
|
suffix string
|
||||||
|
wantIP net.IP
|
||||||
|
wantRes resultCode
|
||||||
|
qtyp uint16
|
||||||
|
}{{
|
||||||
|
name: "success_external",
|
||||||
|
host: examplecom,
|
||||||
|
suffix: defaultAutohostSuffix,
|
||||||
|
wantIP: nil,
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
qtyp: dns.TypeA,
|
||||||
|
}, {
|
||||||
|
name: "success_external_non_a",
|
||||||
|
host: examplecom,
|
||||||
|
suffix: defaultAutohostSuffix,
|
||||||
|
wantIP: nil,
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
qtyp: dns.TypeCNAME,
|
||||||
|
}, {
|
||||||
|
name: "success_internal",
|
||||||
|
host: examplelan,
|
||||||
|
suffix: defaultAutohostSuffix,
|
||||||
|
wantIP: knownIP,
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
qtyp: dns.TypeA,
|
||||||
|
}, {
|
||||||
|
name: "success_internal_unknown",
|
||||||
|
host: "example-new.lan",
|
||||||
|
suffix: defaultAutohostSuffix,
|
||||||
|
wantIP: nil,
|
||||||
|
wantRes: resultCodeFinish,
|
||||||
|
qtyp: dns.TypeA,
|
||||||
|
}, {
|
||||||
|
name: "success_internal_aaaa",
|
||||||
|
host: examplelan,
|
||||||
|
suffix: defaultAutohostSuffix,
|
||||||
|
wantIP: nil,
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
qtyp: dns.TypeAAAA,
|
||||||
|
}, {
|
||||||
|
name: "success_custom_suffix",
|
||||||
|
host: "example.custom",
|
||||||
|
suffix: ".custom.",
|
||||||
|
wantIP: knownIP,
|
||||||
|
wantRes: resultCodeSuccess,
|
||||||
|
qtyp: dns.TypeA,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -89,20 +222,21 @@ func TestServer_ProcessInternalHosts(t *testing.T) {
|
||||||
proxyCtx: &proxy.DNSContext{
|
proxyCtx: &proxy.DNSContext{
|
||||||
Req: req,
|
Req: req,
|
||||||
},
|
},
|
||||||
|
isLocalClient: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
res := s.processInternalHosts(dctx)
|
res := s.processInternalHosts(dctx)
|
||||||
|
pctx := dctx.proxyCtx
|
||||||
assert.Equal(t, tc.wantRes, res)
|
assert.Equal(t, tc.wantRes, res)
|
||||||
|
if tc.wantRes == resultCodeFinish {
|
||||||
|
require.NotNil(t, pctx.Res)
|
||||||
|
assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode)
|
||||||
|
|
||||||
if tc.wantErrMsg == "" {
|
return
|
||||||
assert.NoError(t, dctx.err)
|
|
||||||
} else {
|
|
||||||
require.Error(t, dctx.err)
|
|
||||||
|
|
||||||
assert.Equal(t, tc.wantErrMsg, dctx.err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pctx := dctx.proxyCtx
|
require.NoError(t, dctx.err)
|
||||||
|
|
||||||
if tc.qtyp == dns.TypeAAAA {
|
if tc.qtyp == dns.TypeAAAA {
|
||||||
// TODO(a.garipov): Remove this special handling
|
// TODO(a.garipov): Remove this special handling
|
||||||
// when we fully support AAAA.
|
// when we fully support AAAA.
|
||||||
|
|
Loading…
Reference in New Issue