mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-09-11 10:45:42 +03:00
Pull request: 2499 rewrite: storage vol.2
Merge in DNS/adguard-home from 2499-rewrites-1 to master
Squashed commit of the following:
commit 6303107d6ca7dd88175e4e123128189e5958f060
Merge: e2040b95 09f88cf2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Dec 5 13:38:01 2022 +0200
Merge remote-tracking branch 'origin/master' into 2499-rewrites-1
commit e2040b95dd3157d033d929bb45fc7662b9918a78
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Dec 5 12:00:21 2022 +0200
rewrite: item
commit c7278e8adeec1ba3a090cc93db30c80699617c52
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Sun Dec 4 12:57:59 2022 +0200
rewrite: imp code
commit d23a740262a4fbdd9b25f7449ced707c0d2be634
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:08:25 2022 +0200
rewrite: imp code
commit 773a5211b6662afd03a34219e7114c6f1e2bb579
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:05:20 2022 +0200
rewrite: imp code
commit 48b54e19da9844d9b868d0be7e428ad6bacae6a5
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:02:47 2022 +0200
rewrite: tests item
commit 62af2bd91f5559840e7948ac4bf7c36b1bee1dc2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:11:21 2022 +0200
rewrite: tests
commit f040b609391cb2275b11d4732bbac0380c01de07
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:04:59 2022 +0200
rewrite: imp code
commit 4592b8c4e6107e5a746261d3335282827ce36b74
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:02:31 2022 +0200
rewrite: imp code
commit cc1660695341c558dbac6acaa31ac160a45f6105
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 16:19:27 2022 +0200
rewrite: imp code
commit cf3840b76d45bf319630256c01586159dd1e85fe
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 13:16:40 2022 +0200
rewrite: tests
commit 6fd6f03ca4320d4345032139b43cb457b3ae4278
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 11:14:44 2022 +0200
rewrite: imp code
commit 2ebd2a1e79afc8f486cf6533968c51ca61bc03ab
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed Nov 30 11:43:24 2022 +0200
rewrite: tests
commit 7da987994303a3e7b16eb6b0baaa4b59a52b97be
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed Nov 30 10:54:57 2022 +0200
filtering: imp code
commit ab98efc6710fac7cba28dab5bca9b60e9ec34ef7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Nov 28 13:31:12 2022 +0200
filtering: rewritehttp
This commit is contained in:
parent
09f88cf21d
commit
01652e6ab2
@ -2,7 +2,7 @@ package rewrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@ -26,11 +26,15 @@ func (rw *Item) equal(other *Item) (ok bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
return rw.Domain == other.Domain && rw.Answer == other.Answer
|
||||
return *rw == *other
|
||||
}
|
||||
|
||||
// toRule converts rw to a filter rule.
|
||||
func (rw *Item) toRule() (res string) {
|
||||
if rw == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
domain := strings.ToLower(rw.Domain)
|
||||
|
||||
dType, exception := rw.rewriteParams()
|
||||
@ -53,13 +57,13 @@ func (rw *Item) rewriteParams() (dType uint16, exception bool) {
|
||||
// Go on.
|
||||
}
|
||||
|
||||
ip := net.ParseIP(rw.Answer)
|
||||
if ip == nil {
|
||||
addr, err := netip.ParseAddr(rw.Answer)
|
||||
if err != nil {
|
||||
// TODO(d.kolyshev): Validate rw.Answer as a domain name.
|
||||
return dns.TypeCNAME, false
|
||||
}
|
||||
|
||||
ip4 := ip.To4()
|
||||
if ip4 != nil {
|
||||
if addr.Is4() {
|
||||
dType = dns.TypeA
|
||||
} else {
|
||||
dType = dns.TypeAAAA
|
||||
|
124
internal/filtering/rewrite/item_internal_test.go
Normal file
124
internal/filtering/rewrite/item_internal_test.go
Normal file
@ -0,0 +1,124 @@
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestItem_equal(t *testing.T) {
|
||||
const (
|
||||
testDomain = "example.org"
|
||||
testAnswer = "1.1.1.1"
|
||||
)
|
||||
|
||||
testItem := &Item{
|
||||
Domain: testDomain,
|
||||
Answer: testAnswer,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
left *Item
|
||||
right *Item
|
||||
want bool
|
||||
}{{
|
||||
name: "nil_left",
|
||||
left: nil,
|
||||
right: testItem,
|
||||
want: false,
|
||||
}, {
|
||||
name: "nil_right",
|
||||
left: testItem,
|
||||
right: nil,
|
||||
want: false,
|
||||
}, {
|
||||
name: "nils",
|
||||
left: nil,
|
||||
right: nil,
|
||||
want: true,
|
||||
}, {
|
||||
name: "equal",
|
||||
left: testItem,
|
||||
right: testItem,
|
||||
want: true,
|
||||
}, {
|
||||
name: "distinct",
|
||||
left: testItem,
|
||||
right: &Item{
|
||||
Domain: "other",
|
||||
Answer: "other",
|
||||
},
|
||||
want: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := tc.left.equal(tc.right)
|
||||
assert.Equal(t, tc.want, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestItem_toRule(t *testing.T) {
|
||||
const testDomain = "example.org"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
item *Item
|
||||
want string
|
||||
}{{
|
||||
name: "nil",
|
||||
item: nil,
|
||||
want: "",
|
||||
}, {
|
||||
name: "a_rule",
|
||||
item: &Item{
|
||||
Domain: testDomain,
|
||||
Answer: "1.1.1.1",
|
||||
},
|
||||
want: "|example.org^$dnsrewrite=NOERROR;A;1.1.1.1",
|
||||
}, {
|
||||
name: "aaaa_rule",
|
||||
item: &Item{
|
||||
Domain: testDomain,
|
||||
Answer: "1:2:3::4",
|
||||
},
|
||||
want: "|example.org^$dnsrewrite=NOERROR;AAAA;1:2:3::4",
|
||||
}, {
|
||||
name: "cname_rule",
|
||||
item: &Item{
|
||||
Domain: testDomain,
|
||||
Answer: "other.org",
|
||||
},
|
||||
want: "|example.org^$dnsrewrite=NOERROR;CNAME;other.org",
|
||||
}, {
|
||||
name: "wildcard_rule",
|
||||
item: &Item{
|
||||
Domain: "*.example.org",
|
||||
Answer: "other.org",
|
||||
},
|
||||
want: "|*.example.org^$dnsrewrite=NOERROR;CNAME;other.org",
|
||||
}, {
|
||||
name: "aaaa_exception",
|
||||
item: &Item{
|
||||
Domain: testDomain,
|
||||
Answer: "A",
|
||||
},
|
||||
want: "@@||example.org^$dnstype=A,dnsrewrite",
|
||||
}, {
|
||||
name: "aaaa_exception",
|
||||
item: &Item{
|
||||
Domain: testDomain,
|
||||
Answer: "AAAA",
|
||||
},
|
||||
want: "@@||example.org^$dnstype=AAAA,dnsrewrite",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res := tc.item.toRule()
|
||||
assert.Equal(t, tc.want, res)
|
||||
})
|
||||
}
|
||||
}
|
@ -7,15 +7,18 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/stringutil"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// Storage is a storage for rewrite rules.
|
||||
type Storage interface {
|
||||
// MatchRequest finds a matching rule for the specified request.
|
||||
MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool)
|
||||
// MatchRequest returns matching dnsrewrites for the specified request.
|
||||
MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite)
|
||||
|
||||
// Add adds item to the storage.
|
||||
Add(item *Item) (err error)
|
||||
@ -38,14 +41,14 @@ type DefaultStorage struct {
|
||||
// ruleList is the filtering rule ruleList used by the engine.
|
||||
ruleList filterlist.RuleList
|
||||
|
||||
// rewrites stores the rewrite entries from configuration.
|
||||
rewrites []*Item
|
||||
|
||||
// urlFilterID is the synthetic integer identifier for the urlfilter engine.
|
||||
//
|
||||
// TODO(a.garipov): Change the type to a string in module urlfilter and
|
||||
// remove this crutch.
|
||||
urlFilterID int
|
||||
|
||||
// rewrites stores the rewrite entries from configuration.
|
||||
rewrites []*Item
|
||||
}
|
||||
|
||||
// NewDefaultStorage returns new rewrites storage. listID is used as an
|
||||
@ -72,11 +75,81 @@ func NewDefaultStorage(listID int, rewrites []*Item) (s *DefaultStorage, err err
|
||||
var _ Storage = (*DefaultStorage)(nil)
|
||||
|
||||
// MatchRequest implements the [Storage] interface for *DefaultStorage.
|
||||
func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool) {
|
||||
func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
return s.engine.MatchRequest(dReq)
|
||||
rrules := s.rewriteRulesForReq(dReq)
|
||||
if len(rrules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Check cnames for cycles on initialisation.
|
||||
cnames := stringutil.NewSet()
|
||||
host := dReq.Hostname
|
||||
for len(rrules) > 0 && rrules[0].DNSRewrite != nil && rrules[0].DNSRewrite.NewCNAME != "" {
|
||||
rule := rrules[0]
|
||||
rwAns := rule.DNSRewrite.NewCNAME
|
||||
|
||||
log.Debug("rewrite: cname for %s is %s", host, rwAns)
|
||||
|
||||
if dReq.Hostname == rwAns {
|
||||
// A request for the hostname itself is an exception rule.
|
||||
// TODO(d.kolyshev): Check rewrite of a pattern onto itself.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if host == rwAns && isWildcard(rule.RuleText) {
|
||||
// An "*.example.com → sub.example.com" rewrite matching in a loop.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/4016.
|
||||
|
||||
return []*rules.DNSRewrite{rule.DNSRewrite}
|
||||
}
|
||||
|
||||
if cnames.Has(rwAns) {
|
||||
log.Info("rewrite: cname loop for %q on %q", dReq.Hostname, rwAns)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
cnames.Add(rwAns)
|
||||
|
||||
drules := s.rewriteRulesForReq(&urlfilter.DNSRequest{
|
||||
Hostname: rwAns,
|
||||
DNSType: dReq.DNSType,
|
||||
})
|
||||
if drules != nil {
|
||||
rrules = drules
|
||||
}
|
||||
|
||||
host = rwAns
|
||||
}
|
||||
|
||||
return s.collectDNSRewrites(rrules, dReq.DNSType)
|
||||
}
|
||||
|
||||
// collectDNSRewrites filters DNSRewrite by question type.
|
||||
func (s *DefaultStorage) collectDNSRewrites(
|
||||
rewrites []*rules.NetworkRule,
|
||||
qtyp uint16,
|
||||
) (rws []*rules.DNSRewrite) {
|
||||
for _, rewrite := range rewrites {
|
||||
dnsRewrite := rewrite.DNSRewrite
|
||||
if matchesQType(dnsRewrite, qtyp) {
|
||||
rws = append(rws, dnsRewrite)
|
||||
}
|
||||
}
|
||||
|
||||
return rws
|
||||
}
|
||||
|
||||
// rewriteRulesForReq returns matching dnsrewrite rules.
|
||||
func (s *DefaultStorage) rewriteRulesForReq(dReq *urlfilter.DNSRequest) (rules []*rules.NetworkRule) {
|
||||
res, _ := s.engine.MatchRequest(dReq)
|
||||
|
||||
return res.DNSRewrites()
|
||||
}
|
||||
|
||||
// Add implements the [Storage] interface for *DefaultStorage.
|
||||
@ -122,6 +195,7 @@ func (s *DefaultStorage) List() (items []*Item) {
|
||||
|
||||
// resetRules resets the filtering rules.
|
||||
func (s *DefaultStorage) resetRules() (err error) {
|
||||
// TODO(a.garipov): Use strings.Builder.
|
||||
var rulesText []string
|
||||
for _, rewrite := range s.rewrites {
|
||||
rulesText = append(rulesText, rewrite.toRule())
|
||||
@ -141,7 +215,27 @@ func (s *DefaultStorage) resetRules() (err error) {
|
||||
s.ruleList = strList
|
||||
s.engine = urlfilter.NewDNSEngine(rs)
|
||||
|
||||
log.Info("filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount)
|
||||
log.Info("rewrite: filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchesQType returns true if dnsrewrite matches the question type qt.
|
||||
func matchesQType(dnsrr *rules.DNSRewrite, qt uint16) (ok bool) {
|
||||
// Add CNAMEs, since they match for all types requests.
|
||||
if dnsrr.RRType == dns.TypeCNAME {
|
||||
return true
|
||||
}
|
||||
|
||||
// Reject types other than A and AAAA.
|
||||
if qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
return false
|
||||
}
|
||||
|
||||
return dnsrr.RRType == qt
|
||||
}
|
||||
|
||||
// isWildcard returns true if pat is a wildcard domain pattern.
|
||||
func isWildcard(pat string) (res bool) {
|
||||
return strings.HasPrefix(pat, "|*.")
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -38,3 +43,416 @@ func TestDefaultStorage_CRUD(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, s.List(), 0)
|
||||
}
|
||||
|
||||
func TestDefaultStorage_MatchRequest(t *testing.T) {
|
||||
items := []*Item{{
|
||||
// This one and below are about CNAME, A and AAAA.
|
||||
Domain: "somecname",
|
||||
Answer: "somehost.com",
|
||||
}, {
|
||||
Domain: "somehost.com",
|
||||
Answer: "0.0.0.0",
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: "1.2.3.4",
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: "1.2.3.5",
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: "1:2:3::4",
|
||||
}, {
|
||||
Domain: "www.host.com",
|
||||
Answer: "host.com",
|
||||
}, {
|
||||
// This one is a wildcard.
|
||||
Domain: "*.host.com",
|
||||
Answer: "1.2.3.5",
|
||||
}, {
|
||||
// This one and below are about wildcard overriding.
|
||||
Domain: "a.host.com",
|
||||
Answer: "1.2.3.4",
|
||||
}, {
|
||||
// This one is about CNAME and wildcard interacting.
|
||||
Domain: "*.host2.com",
|
||||
Answer: "host.com",
|
||||
}, {
|
||||
// This one and below are about 2 level CNAME.
|
||||
Domain: "b.host.com",
|
||||
Answer: "somecname",
|
||||
}, {
|
||||
// This one and below are about 2 level CNAME and wildcard.
|
||||
Domain: "b.host3.com",
|
||||
Answer: "a.host3.com",
|
||||
}, {
|
||||
Domain: "a.host3.com",
|
||||
Answer: "x.host.com",
|
||||
}, {
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: "1.2.3.6",
|
||||
}, {
|
||||
Domain: "*.hostboth.com",
|
||||
Answer: "1234::5678",
|
||||
}, {
|
||||
Domain: "BIGHOST.COM",
|
||||
Answer: "1.2.3.7",
|
||||
}, {
|
||||
Domain: "*.issue4016.com",
|
||||
Answer: "sub.issue4016.com",
|
||||
}}
|
||||
|
||||
s, err := NewDefaultStorage(-1, items)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
wantDNSRewrites []*rules.DNSRewrite
|
||||
dtyp uint16
|
||||
}{{
|
||||
name: "not_filtered_not_found",
|
||||
host: "hoost.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "not_filtered_qtype",
|
||||
host: "www.host.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeMX,
|
||||
}, {
|
||||
name: "rewritten_a",
|
||||
host: "www.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 4}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
Value: net.IP{1, 2, 3, 5}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "rewritten_aaaa",
|
||||
host: "www.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.ParseIP("1:2:3::4"),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
dtyp: dns.TypeAAAA,
|
||||
}, {
|
||||
name: "wildcard_match",
|
||||
host: "abc.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 5}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
//}, {
|
||||
// TODO(d.kolyshev): This is about matching in urlfilter.
|
||||
// name: "wildcard_override",
|
||||
// host: "a.host.com",
|
||||
// wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
// Value: net.IP{1, 2, 3, 4}.To16(),
|
||||
// NewCNAME: "",
|
||||
// RCode: dns.RcodeSuccess,
|
||||
// RRType: dns.TypeA,
|
||||
// }},
|
||||
// dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "wildcard_cname_interaction",
|
||||
host: "www.host2.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 4}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}, {
|
||||
Value: net.IP{1, 2, 3, 5}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "two_cnames",
|
||||
host: "b.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{0, 0, 0, 0}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "two_cnames_and_wildcard",
|
||||
host: "b.host3.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 5}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "issue3343",
|
||||
host: "www.hostboth.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.ParseIP("1234::5678"),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
dtyp: dns.TypeAAAA,
|
||||
}, {
|
||||
name: "issue3351",
|
||||
host: "bighost.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 7}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "issue4008",
|
||||
host: "somehost.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeHTTPS,
|
||||
}, {
|
||||
name: "issue4016",
|
||||
host: "www.issue4016.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: nil,
|
||||
NewCNAME: "sub.issue4016.com",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeNone,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "issue4016_self",
|
||||
host: "sub.issue4016.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: tc.host,
|
||||
DNSType: tc.dtyp,
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultStorage_MatchRequest_Levels(t *testing.T) {
|
||||
// Exact host, wildcard L2, wildcard L3.
|
||||
items := []*Item{{
|
||||
Domain: "host.com",
|
||||
Answer: "1.1.1.1",
|
||||
}, {
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
}, {
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "3.3.3.3",
|
||||
}}
|
||||
|
||||
s, err := NewDefaultStorage(-1, items)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
wantDNSRewrites []*rules.DNSRewrite
|
||||
dtyp uint16
|
||||
}{{
|
||||
name: "exact_match",
|
||||
host: "host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 1, 1, 1}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "l2_match",
|
||||
host: "sub.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{2, 2, 2, 2}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
//}, {
|
||||
// TODO(d.kolyshev): This is about matching in urlfilter.
|
||||
// name: "l3_match",
|
||||
// host: "my.sub.host.com",
|
||||
// wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
// Value: net.IP{3, 3, 3, 3}.To16(),
|
||||
// NewCNAME: "",
|
||||
// RCode: dns.RcodeSuccess,
|
||||
// RRType: dns.TypeA,
|
||||
// }},
|
||||
// dtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: tc.host,
|
||||
DNSType: tc.dtyp,
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) {
|
||||
// Wildcard and exception for a sub-domain.
|
||||
items := []*Item{{
|
||||
Domain: "*.host.com",
|
||||
Answer: "2.2.2.2",
|
||||
}, {
|
||||
Domain: "sub.host.com",
|
||||
Answer: "sub.host.com",
|
||||
}, {
|
||||
Domain: "*.sub.host.com",
|
||||
Answer: "*.sub.host.com",
|
||||
}}
|
||||
|
||||
s, err := NewDefaultStorage(-1, items)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
wantDNSRewrites []*rules.DNSRewrite
|
||||
dtyp uint16
|
||||
}{{
|
||||
name: "match_subdomain",
|
||||
host: "my.host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{2, 2, 2, 2}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "exception_cname",
|
||||
host: "sub.host.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeA,
|
||||
//}, {
|
||||
// TODO(d.kolyshev): This is about matching in urlfilter.
|
||||
// name: "exception_wildcard",
|
||||
// host: "my.sub.host.com",
|
||||
// wantDNSRewrites: nil,
|
||||
// dtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: tc.host,
|
||||
DNSType: tc.dtyp,
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) {
|
||||
// Exception for AAAA record.
|
||||
items := []*Item{{
|
||||
Domain: "host.com",
|
||||
Answer: "1.2.3.4",
|
||||
}, {
|
||||
Domain: "host.com",
|
||||
Answer: "AAAA",
|
||||
}, {
|
||||
Domain: "host2.com",
|
||||
Answer: "::1",
|
||||
}, {
|
||||
Domain: "host2.com",
|
||||
Answer: "A",
|
||||
}, {
|
||||
Domain: "host3.com",
|
||||
Answer: "A",
|
||||
}}
|
||||
|
||||
s, err := NewDefaultStorage(-1, items)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
host string
|
||||
wantDNSRewrites []*rules.DNSRewrite
|
||||
dtyp uint16
|
||||
}{{
|
||||
name: "match_A",
|
||||
host: "host.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.IP{1, 2, 3, 4}.To16(),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeA,
|
||||
}},
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "exception_AAAA_host.com",
|
||||
host: "host.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeAAAA,
|
||||
}, {
|
||||
name: "exception_A_host2.com",
|
||||
host: "host2.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "match_AAAA_host2.com",
|
||||
host: "host2.com",
|
||||
wantDNSRewrites: []*rules.DNSRewrite{{
|
||||
Value: net.ParseIP("::1"),
|
||||
NewCNAME: "",
|
||||
RCode: dns.RcodeSuccess,
|
||||
RRType: dns.TypeAAAA,
|
||||
}},
|
||||
dtyp: dns.TypeAAAA,
|
||||
}, {
|
||||
name: "exception_A_host3.com",
|
||||
host: "host3.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeA,
|
||||
}, {
|
||||
name: "match_AAAA_host3.com",
|
||||
host: "host3.com",
|
||||
wantDNSRewrites: nil,
|
||||
dtyp: dns.TypeAAAA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{
|
||||
Hostname: tc.host,
|
||||
DNSType: tc.dtyp,
|
||||
})
|
||||
|
||||
assert.Equal(t, tc.wantDNSRewrites, dnsRewrites)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
93
internal/filtering/rewritehttp.go
Normal file
93
internal/filtering/rewritehttp.go
Normal file
@ -0,0 +1,93 @@
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// TODO(d.kolyshev): Use [rewrite.Item] instead.
|
||||
type rewriteEntryJSON struct {
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||
arr := []*rewriteEntryJSON{}
|
||||
|
||||
d.confLock.Lock()
|
||||
for _, ent := range d.Config.Rewrites {
|
||||
jsent := rewriteEntryJSON{
|
||||
Domain: ent.Domain,
|
||||
Answer: ent.Answer,
|
||||
}
|
||||
arr = append(arr, &jsent)
|
||||
}
|
||||
d.confLock.Unlock()
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, arr)
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
rwJSON := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&rwJSON)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rw := &LegacyRewrite{
|
||||
Domain: rwJSON.Domain,
|
||||
Answer: rwJSON.Answer,
|
||||
}
|
||||
|
||||
err = rw.normalize()
|
||||
if err != nil {
|
||||
// Shouldn't happen currently, since normalize only returns a non-nil
|
||||
// error when a rewrite is nil, but be change-proof.
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
d.confLock.Lock()
|
||||
d.Config.Rewrites = append(d.Config.Rewrites, rw)
|
||||
d.confLock.Unlock()
|
||||
log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites))
|
||||
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
||||
jsent := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entDel := &LegacyRewrite{
|
||||
Domain: jsent.Domain,
|
||||
Answer: jsent.Answer,
|
||||
}
|
||||
arr := []*LegacyRewrite{}
|
||||
|
||||
d.confLock.Lock()
|
||||
for _, ent := range d.Config.Rewrites {
|
||||
if ent.equal(entDel) {
|
||||
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
arr = append(arr, ent)
|
||||
}
|
||||
d.Config.Rewrites = arr
|
||||
d.confLock.Unlock()
|
||||
|
||||
d.Config.ConfigModified()
|
||||
}
|
@ -3,22 +3,16 @@
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// TODO(d.kolyshev): Rename this file to rewritehttp.go.
|
||||
|
||||
// LegacyRewrite is a single legacy DNS rewrite record.
|
||||
//
|
||||
// Instances of *LegacyRewrite must never be nil.
|
||||
@ -223,86 +217,3 @@ func max(a, b int) int {
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
type rewriteEntryJSON struct {
|
||||
Domain string `json:"domain"`
|
||||
Answer string `json:"answer"`
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
|
||||
arr := []*rewriteEntryJSON{}
|
||||
|
||||
d.confLock.Lock()
|
||||
for _, ent := range d.Config.Rewrites {
|
||||
jsent := rewriteEntryJSON{
|
||||
Domain: ent.Domain,
|
||||
Answer: ent.Answer,
|
||||
}
|
||||
arr = append(arr, &jsent)
|
||||
}
|
||||
d.confLock.Unlock()
|
||||
|
||||
_ = aghhttp.WriteJSONResponse(w, r, arr)
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
|
||||
rwJSON := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&rwJSON)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rw := &LegacyRewrite{
|
||||
Domain: rwJSON.Domain,
|
||||
Answer: rwJSON.Answer,
|
||||
}
|
||||
|
||||
err = rw.normalize()
|
||||
if err != nil {
|
||||
// Shouldn't happen currently, since normalize only returns a non-nil
|
||||
// error when a rewrite is nil, but be change-proof.
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
d.confLock.Lock()
|
||||
d.Config.Rewrites = append(d.Config.Rewrites, rw)
|
||||
d.confLock.Unlock()
|
||||
log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites))
|
||||
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) {
|
||||
jsent := rewriteEntryJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&jsent)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
entDel := &LegacyRewrite{
|
||||
Domain: jsent.Domain,
|
||||
Answer: jsent.Answer,
|
||||
}
|
||||
arr := []*LegacyRewrite{}
|
||||
|
||||
d.confLock.Lock()
|
||||
for _, ent := range d.Config.Rewrites {
|
||||
if ent.equal(entDel) {
|
||||
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
arr = append(arr, ent)
|
||||
}
|
||||
d.Config.Rewrites = arr
|
||||
d.confLock.Unlock()
|
||||
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
)
|
||||
|
||||
// TODO(e.burkov): All the tests in this file may and should me merged together.
|
||||
// TODO(d.kolyshev): Move these tests to rewrite package.
|
||||
|
||||
func TestRewrites(t *testing.T) {
|
||||
d, _ := newForTest(t, nil, nil)
|
||||
|
@ -227,9 +227,9 @@ gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/
|
||||
|
||||
# Apply stricter standards to new or somewhat refactored code.
|
||||
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
|
||||
./internal/aghtest/ ./internal/dnsforward/ ./internal/stats/\
|
||||
./internal/tools/ ./internal/updater/ ./internal/next/ ./internal/version/\
|
||||
./scripts/vetted-filters/ ./main.go
|
||||
./internal/aghtest/ ./internal/dnsforward/ ./internal/filtering/rewrite/\
|
||||
./internal/stats/ ./internal/tools/ ./internal/updater/ ./internal/next/\
|
||||
./internal/version/ ./scripts/vetted-filters/ ./main.go
|
||||
|
||||
ineffassign ./...
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user