Skip to content

Commit

Permalink
all: https filtering hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizzick committed Aug 4, 2023
1 parent 5eb3cd0 commit 1d2d1aa
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 3 deletions.
6 changes: 6 additions & 0 deletions internal/aghtest/aghtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
"github.com/AdguardTeam/golibs/log"
)

// ReqHost is the common request host for filtering tests.
const ReqHost = "www.host.example"

// ReqFQDN is the common request FQDN for filtering tests.
const ReqFQDN = ReqHost + "."

// ReplaceLogWriter moves logger output to w and uses Cleanup method of t to
// revert changes.
func ReplaceLogWriter(t testing.TB, w io.Writer) {
Expand Down
11 changes: 11 additions & 0 deletions internal/dnsforward/dnsforward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,17 @@ func createTestMessageWithType(host string, qtype uint16) *dns.Msg {
return req
}

// newResp returns the new DNS response with response code set to rcode, req
// used as request, and rrs added.
func newResp(rcode int, req *dns.Msg, ans []dns.RR) (resp *dns.Msg) {
resp = (&dns.Msg{}).SetRcode(req, rcode)
resp.RecursionAvailable = true
resp.Compress = true
resp.Answer = ans

return resp
}

func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
assertResponse(t, reply, net.IP{8, 8, 8, 8})
}
Expand Down
59 changes: 57 additions & 2 deletions internal/dnsforward/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func (s *Server) clientRequestFilteringSettings(dctx *dnsContext) (setts *filter

// filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the
// request was filtered.
//
// TODO(d.kolyshev): Filter HTTPS
func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err error) {
pctx := dctx.proxyCtx
req := pctx.Req
Expand Down Expand Up @@ -176,19 +178,26 @@ func (s *Server) filterDNSResponse(
case *dns.CNAME:
host = strings.TrimSuffix(a.Target, ".")
rrtype = dns.TypeCNAME

res, err = s.checkHostRules(host, rrtype, setts)
case *dns.A:
host = a.A.String()
rrtype = dns.TypeA

res, err = s.checkHostRules(host, rrtype, setts)
case *dns.AAAA:
host = a.AAAA.String()
rrtype = dns.TypeAAAA

res, err = s.checkHostRules(host, rrtype, setts)
case *dns.SVCB:
res, err = s.filterHTTPSRecords(a, setts)
default:
continue
}

log.Debug("dnsforward: checking %s %s for %s", dns.Type(rrtype), host, a.Header().Name)
log.Debug("dnsforward: checked %s %s for %s", dns.Type(rrtype), host, a.Header().Name)

res, err = s.checkHostRules(host, rrtype, setts)
if err != nil {
return nil, err
} else if res == nil {
Expand All @@ -203,3 +212,49 @@ func (s *Server) filterDNSResponse(

return nil, nil
}

// filterHTTPSRecords filters HTTPS answers information through all rule list
// filters of the server filters.
func (s *Server) filterHTTPSRecords(
rr *dns.SVCB,
setts *filtering.Settings,
) (r *filtering.Result, err error) {
for _, kv := range rr.Value {
switch kv.Key() {
case dns.SVCB_IPV4HINT, dns.SVCB_IPV6HINT:
r, err = s.filterSVCBHint(kv.String(), setts)
if err != nil {
// Don't wrap the error, since it's informative enough as is.
return nil, err
}

if r != nil {
return r, nil
}
default:
// Go on.
}
}

return nil, nil
}

// filterSVCBHint filters SVCB hint information.
func (s *Server) filterSVCBHint(
hint string,
setts *filtering.Settings,
) (res *filtering.Result, err error) {
for _, h := range strings.Split(hint, ",") {
res, err = s.checkHostRules(h, dns.TypeHTTPS, setts)
if err != nil {
// Don't wrap the error, since it's informative enough as is.
return nil, err
}

if res != nil && res.IsFiltered {
return res, nil
}
}

return nil, nil
}
184 changes: 183 additions & 1 deletion internal/dnsforward/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dnsforward

import (
"net"
"net/netip"
"testing"

"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
Expand All @@ -14,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
)

func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
rules := `
||blocked.domain^
@@||allowed.domain^
Expand Down Expand Up @@ -168,3 +169,184 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
})
}
}

func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
const (
passedIPv4Str = "1.1.1.1"
blockedIPv4Str = "1.2.3.4"
blockedIPv6Str = "1234::cdef"
blockRules = blockedIPv4Str + "\n" + blockedIPv6Str + "\n"
)

var (
passedIPv4 net.IP = netip.MustParseAddr(passedIPv4Str).AsSlice()
blockedIPv4 net.IP = netip.MustParseAddr(blockedIPv4Str).AsSlice()
blockedIPv6 net.IP = netip.MustParseAddr(blockedIPv6Str).AsSlice()
)

filters := []filtering.Filter{{
ID: 0, Data: []byte(blockRules),
}}

f, err := filtering.New(&filtering.Config{}, filters)
require.NoError(t, err)

f.SetEnabled(true)

s, err := NewServer(DNSCreateParams{
DHCPServer: testDHCP,
DNSFilter: f,
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
})
require.NoError(t, err)

testCases := []struct {
name string
reqFQDN string
wantRule string
respAns []dns.RR
qType uint16
}{{
name: "pass",
reqFQDN: aghtest.ReqFQDN,
wantRule: "",
respAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: passedIPv4,
}},
qType: dns.TypeA,
}, {
name: "ipv4",
reqFQDN: aghtest.ReqFQDN,
wantRule: blockedIPv4Str,
respAns: []dns.RR{&dns.A{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
},
A: blockedIPv4,
}},
qType: dns.TypeA,
}, {
name: "ipv6",
reqFQDN: aghtest.ReqFQDN,
wantRule: blockedIPv6Str,
respAns: []dns.RR{&dns.AAAA{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeAAAA,
Class: dns.ClassINET,
},
AAAA: blockedIPv6,
}},
qType: dns.TypeAAAA,
}, {
name: "ipv4hint",
reqFQDN: aghtest.ReqFQDN,
wantRule: blockedIPv4Str,
respAns: []dns.RR{&dns.SVCB{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
},
Target: aghtest.ReqFQDN,
Value: []dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{}},
},
}},
qType: dns.TypeHTTPS,
}, {
name: "ipv6hint",
reqFQDN: aghtest.ReqFQDN,
wantRule: blockedIPv6Str,
respAns: []dns.RR{&dns.SVCB{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
},
Target: aghtest.ReqFQDN,
Value: []dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{}},
&dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}},
},
}},
qType: dns.TypeHTTPS,
}, {
name: "ipv4_ipv6_hints",
reqFQDN: aghtest.ReqFQDN,
wantRule: blockedIPv4Str,
respAns: []dns.RR{&dns.SVCB{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
},
Target: aghtest.ReqFQDN,
Value: []dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{blockedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{blockedIPv6}},
},
}},
qType: dns.TypeHTTPS,
}, {
name: "pass_hints",
reqFQDN: aghtest.ReqFQDN,
wantRule: "",
respAns: []dns.RR{&dns.SVCB{
Hdr: dns.RR_Header{
Name: aghtest.ReqFQDN,
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
},
Target: aghtest.ReqFQDN,
Value: []dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{passedIPv4}},
&dns.SVCBIPv6Hint{Hint: []net.IP{}},
},
}},
qType: dns.TypeHTTPS,
}}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := createTestMessageWithType(tc.reqFQDN, tc.qType)
resp := newResp(dns.RcodeSuccess, req, tc.respAns)

pctx := &proxy.DNSContext{
Proto: proxy.ProtoUDP,
Req: req,
Res: resp,
Addr: &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: 1},
}

res, rErr := s.filterDNSResponse(pctx, &filtering.Settings{
ProtectionEnabled: true,
FilteringEnabled: true,
})
require.NoError(t, rErr)

if tc.wantRule == "" {
assert.Nil(t, res)

return
}

want := &filtering.Result{
IsFiltered: true,
Reason: filtering.FilteredBlockList,
Rules: []*filtering.ResultRule{{
Text: tc.wantRule,
}},
}
assert.Equal(t, want, res)
})
}
}

0 comments on commit 1d2d1aa

Please sign in to comment.