Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pull request: 2499 rewrite: storage vol.1
Merge in DNS/adguard-home from 2499-rewrites to master Squashed commit of the following: commit 3f5f8e1 Merge: c84a86f fafd7a1 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Nov 28 13:13:31 2022 +0200 Merge remote-tracking branch 'origin/master' into 2499-rewrites commit c84a86f Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Nov 28 12:50:26 2022 +0200 rewrite: todos commit 3b33a79 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Nov 28 11:45:05 2022 +0200 rewrite: todos commit 1502299 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 12:15:27 2022 +0200 rewrite: imp code commit b3c1949 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:33:25 2022 +0200 rewrite: imp code commit 80fe50a Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:13:39 2022 +0200 rewrite: imp code commit 5288ede Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:04:11 2022 +0200 Revert "all: rewrite" This reverts commit 32ad8d7. commit cff6494 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:04:11 2022 +0200 Revert "all: rewrite" This reverts commit 65e44e9. commit e0fe877 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:04:11 2022 +0200 Revert "filtering: imp code" This reverts commit c882da3. commit 8e3f9d4 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 11:04:11 2022 +0200 Revert "rewrite: imp code" This reverts commit ce23329. commit ce23329 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri Nov 25 10:56:48 2022 +0200 rewrite: imp code commit c882da3 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Nov 24 13:39:26 2022 +0200 filtering: imp code commit 65e44e9 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Nov 24 13:25:10 2022 +0200 all: rewrite commit 32ad8d7 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu Nov 24 13:19:55 2022 +0200 all: rewrite commit 941538a Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Nov 22 12:54:55 2022 +0200 rewrite: storage tests commit 0a1ad86 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue Nov 22 12:46:56 2022 +0200 rewrite: imp code commit f10a453 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon Nov 21 14:29:44 2022 +0200 rewrite: storage commit ff91bb8 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Sun Nov 20 13:25:05 2022 +0200 rewrite: storage
- Loading branch information
Showing
6 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package rewrite | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strings" | ||
|
||
"github.com/miekg/dns" | ||
) | ||
|
||
// Item is a single DNS rewrite record. | ||
type Item struct { | ||
// Domain is the domain pattern for which this rewrite should work. | ||
Domain string `yaml:"domain"` | ||
|
||
// Answer is the IP address, canonical name, or one of the special | ||
// values: "A" or "AAAA". | ||
Answer string `yaml:"answer"` | ||
} | ||
|
||
// equal returns true if rw is equal to other. | ||
func (rw *Item) equal(other *Item) (ok bool) { | ||
if rw == nil { | ||
return other == nil | ||
} else if other == nil { | ||
return false | ||
} | ||
|
||
return rw.Domain == other.Domain && rw.Answer == other.Answer | ||
} | ||
|
||
// toRule converts rw to a filter rule. | ||
func (rw *Item) toRule() (res string) { | ||
domain := strings.ToLower(rw.Domain) | ||
|
||
dType, exception := rw.rewriteParams() | ||
dTypeKey := dns.TypeToString[dType] | ||
if exception { | ||
return fmt.Sprintf("@@||%s^$dnstype=%s,dnsrewrite", domain, dTypeKey) | ||
} | ||
|
||
return fmt.Sprintf("|%s^$dnsrewrite=NOERROR;%s;%s", domain, dTypeKey, rw.Answer) | ||
} | ||
|
||
// rewriteParams returns dns request type and exception flag for rw. | ||
func (rw *Item) rewriteParams() (dType uint16, exception bool) { | ||
switch rw.Answer { | ||
case "AAAA": | ||
return dns.TypeAAAA, true | ||
case "A": | ||
return dns.TypeA, true | ||
default: | ||
// Go on. | ||
} | ||
|
||
ip := net.ParseIP(rw.Answer) | ||
if ip == nil { | ||
return dns.TypeCNAME, false | ||
} | ||
|
||
ip4 := ip.To4() | ||
if ip4 != nil { | ||
dType = dns.TypeA | ||
} else { | ||
dType = dns.TypeAAAA | ||
} | ||
|
||
return dType, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Package rewrite implements DNS Rewrites storage and request matching. | ||
package rewrite | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/AdguardTeam/golibs/log" | ||
"github.com/AdguardTeam/urlfilter" | ||
"github.com/AdguardTeam/urlfilter/filterlist" | ||
"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) | ||
|
||
// Add adds item to the storage. | ||
Add(item *Item) (err error) | ||
|
||
// Remove deletes item from the storage. | ||
Remove(item *Item) (err error) | ||
|
||
// List returns all items from the storage. | ||
List() (items []*Item) | ||
} | ||
|
||
// DefaultStorage is the default storage for rewrite rules. | ||
type DefaultStorage struct { | ||
// mu protects items. | ||
mu *sync.RWMutex | ||
|
||
// engine is the DNS filtering engine. | ||
engine *urlfilter.DNSEngine | ||
|
||
// ruleList is the filtering rule ruleList used by the engine. | ||
ruleList filterlist.RuleList | ||
|
||
// 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 | ||
// identifier of the underlying rules list. rewrites must not be nil. | ||
func NewDefaultStorage(listID int, rewrites []*Item) (s *DefaultStorage, err error) { | ||
s = &DefaultStorage{ | ||
mu: &sync.RWMutex{}, | ||
urlFilterID: listID, | ||
rewrites: rewrites, | ||
} | ||
|
||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
|
||
err = s.resetRules() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return s, nil | ||
} | ||
|
||
// type check | ||
var _ Storage = (*DefaultStorage)(nil) | ||
|
||
// MatchRequest implements the [Storage] interface for *DefaultStorage. | ||
func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool) { | ||
s.mu.RLock() | ||
defer s.mu.RUnlock() | ||
|
||
return s.engine.MatchRequest(dReq) | ||
} | ||
|
||
// Add implements the [Storage] interface for *DefaultStorage. | ||
func (s *DefaultStorage) Add(item *Item) (err error) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
|
||
// TODO(d.kolyshev): Handle duplicate items. | ||
s.rewrites = append(s.rewrites, item) | ||
|
||
return s.resetRules() | ||
} | ||
|
||
// Remove implements the [Storage] interface for *DefaultStorage. | ||
func (s *DefaultStorage) Remove(item *Item) (err error) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
|
||
arr := []*Item{} | ||
|
||
// TODO(d.kolyshev): Use slices.IndexFunc + slices.Delete? | ||
for _, ent := range s.rewrites { | ||
if ent.equal(item) { | ||
log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer) | ||
|
||
continue | ||
} | ||
|
||
arr = append(arr, ent) | ||
} | ||
s.rewrites = arr | ||
|
||
return s.resetRules() | ||
} | ||
|
||
// List implements the [Storage] interface for *DefaultStorage. | ||
func (s *DefaultStorage) List() (items []*Item) { | ||
s.mu.RLock() | ||
defer s.mu.RUnlock() | ||
|
||
return slices.Clone(s.rewrites) | ||
} | ||
|
||
// resetRules resets the filtering rules. | ||
func (s *DefaultStorage) resetRules() (err error) { | ||
var rulesText []string | ||
for _, rewrite := range s.rewrites { | ||
rulesText = append(rulesText, rewrite.toRule()) | ||
} | ||
|
||
strList := &filterlist.StringRuleList{ | ||
ID: s.urlFilterID, | ||
RulesText: strings.Join(rulesText, "\n"), | ||
IgnoreCosmetic: true, | ||
} | ||
|
||
rs, err := filterlist.NewRuleStorage([]filterlist.RuleList{strList}) | ||
if err != nil { | ||
return fmt.Errorf("creating list storage: %w", err) | ||
} | ||
|
||
s.ruleList = strList | ||
s.engine = urlfilter.NewDNSEngine(rs) | ||
|
||
log.Info("filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package rewrite | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNewDefaultStorage(t *testing.T) { | ||
items := []*Item{{ | ||
Domain: "example.com", | ||
Answer: "answer.com", | ||
}} | ||
|
||
s, err := NewDefaultStorage(-1, items) | ||
require.NoError(t, err) | ||
|
||
require.Len(t, s.List(), 1) | ||
} | ||
|
||
func TestDefaultStorage_CRUD(t *testing.T) { | ||
var items []*Item | ||
|
||
s, err := NewDefaultStorage(-1, items) | ||
require.NoError(t, err) | ||
require.Len(t, s.List(), 0) | ||
|
||
item := &Item{Domain: "example.com", Answer: "answer.com"} | ||
|
||
err = s.Add(item) | ||
require.NoError(t, err) | ||
|
||
list := s.List() | ||
require.Len(t, list, 1) | ||
require.True(t, item.equal(list[0])) | ||
|
||
err = s.Remove(item) | ||
require.NoError(t, err) | ||
require.Len(t, s.List(), 0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters