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:
Dimitry Kolyshev 2022-12-05 14:46:51 +03:00
parent 09f88cf21d
commit 01652e6ab2
8 changed files with 750 additions and 107 deletions

View File

@ -2,7 +2,7 @@ package rewrite
import ( import (
"fmt" "fmt"
"net" "net/netip"
"strings" "strings"
"github.com/miekg/dns" "github.com/miekg/dns"
@ -26,11 +26,15 @@ func (rw *Item) equal(other *Item) (ok bool) {
return false return false
} }
return rw.Domain == other.Domain && rw.Answer == other.Answer return *rw == *other
} }
// toRule converts rw to a filter rule. // toRule converts rw to a filter rule.
func (rw *Item) toRule() (res string) { func (rw *Item) toRule() (res string) {
if rw == nil {
return ""
}
domain := strings.ToLower(rw.Domain) domain := strings.ToLower(rw.Domain)
dType, exception := rw.rewriteParams() dType, exception := rw.rewriteParams()
@ -53,13 +57,13 @@ func (rw *Item) rewriteParams() (dType uint16, exception bool) {
// Go on. // Go on.
} }
ip := net.ParseIP(rw.Answer) addr, err := netip.ParseAddr(rw.Answer)
if ip == nil { if err != nil {
// TODO(d.kolyshev): Validate rw.Answer as a domain name.
return dns.TypeCNAME, false return dns.TypeCNAME, false
} }
ip4 := ip.To4() if addr.Is4() {
if ip4 != nil {
dType = dns.TypeA dType = dns.TypeA
} else { } else {
dType = dns.TypeAAAA dType = dns.TypeAAAA

View 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)
})
}
}

View File

@ -7,15 +7,18 @@ import (
"sync" "sync"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist" "github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
// Storage is a storage for rewrite rules. // Storage is a storage for rewrite rules.
type Storage interface { type Storage interface {
// MatchRequest finds a matching rule for the specified request. // MatchRequest returns matching dnsrewrites for the specified request.
MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool) MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite)
// Add adds item to the storage. // Add adds item to the storage.
Add(item *Item) (err error) Add(item *Item) (err error)
@ -38,14 +41,14 @@ type DefaultStorage struct {
// ruleList is the filtering rule ruleList used by the engine. // ruleList is the filtering rule ruleList used by the engine.
ruleList filterlist.RuleList ruleList filterlist.RuleList
// rewrites stores the rewrite entries from configuration.
rewrites []*Item
// urlFilterID is the synthetic integer identifier for the urlfilter engine. // urlFilterID is the synthetic integer identifier for the urlfilter engine.
// //
// TODO(a.garipov): Change the type to a string in module urlfilter and // TODO(a.garipov): Change the type to a string in module urlfilter and
// remove this crutch. // remove this crutch.
urlFilterID int urlFilterID int
// rewrites stores the rewrite entries from configuration.
rewrites []*Item
} }
// NewDefaultStorage returns new rewrites storage. listID is used as an // 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) var _ Storage = (*DefaultStorage)(nil)
// MatchRequest implements the [Storage] interface for *DefaultStorage. // 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() s.mu.RLock()
defer s.mu.RUnlock() 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. // Add implements the [Storage] interface for *DefaultStorage.
@ -122,6 +195,7 @@ func (s *DefaultStorage) List() (items []*Item) {
// resetRules resets the filtering rules. // resetRules resets the filtering rules.
func (s *DefaultStorage) resetRules() (err error) { func (s *DefaultStorage) resetRules() (err error) {
// TODO(a.garipov): Use strings.Builder.
var rulesText []string var rulesText []string
for _, rewrite := range s.rewrites { for _, rewrite := range s.rewrites {
rulesText = append(rulesText, rewrite.toRule()) rulesText = append(rulesText, rewrite.toRule())
@ -141,7 +215,27 @@ func (s *DefaultStorage) resetRules() (err error) {
s.ruleList = strList s.ruleList = strList
s.engine = urlfilter.NewDNSEngine(rs) 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 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, "|*.")
}

View File

@ -1,8 +1,13 @@
package rewrite package rewrite
import ( import (
"net"
"testing" "testing"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -38,3 +43,416 @@ func TestDefaultStorage_CRUD(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, s.List(), 0) 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)
})
}
}

View 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()
}

View File

@ -3,22 +3,16 @@
package filtering package filtering
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"net/http"
"sort" "sort"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns" "github.com/miekg/dns"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
// TODO(d.kolyshev): Rename this file to rewritehttp.go.
// LegacyRewrite is a single legacy DNS rewrite record. // LegacyRewrite is a single legacy DNS rewrite record.
// //
// Instances of *LegacyRewrite must never be nil. // Instances of *LegacyRewrite must never be nil.
@ -223,86 +217,3 @@ func max(a, b int) int {
return b 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()
}

View File

@ -10,7 +10,6 @@ import (
) )
// TODO(e.burkov): All the tests in this file may and should me merged together. // 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) { func TestRewrites(t *testing.T) {
d, _ := newForTest(t, nil, nil) d, _ := newForTest(t, nil, nil)

View File

@ -227,9 +227,9 @@ gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/
# Apply stricter standards to new or somewhat refactored code. # Apply stricter standards to new or somewhat refactored code.
gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\ gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\
./internal/aghtest/ ./internal/dnsforward/ ./internal/stats/\ ./internal/aghtest/ ./internal/dnsforward/ ./internal/filtering/rewrite/\
./internal/tools/ ./internal/updater/ ./internal/next/ ./internal/version/\ ./internal/stats/ ./internal/tools/ ./internal/updater/ ./internal/next/\
./scripts/vetted-filters/ ./main.go ./internal/version/ ./scripts/vetted-filters/ ./main.go
ineffassign ./... ineffassign ./...