Skip to content

Commit

Permalink
Pull request: 2499 rewrite: storage vol.1
Browse files Browse the repository at this point in the history
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
Mizzick committed Nov 28, 2022
1 parent fafd7a1 commit e6f8aee
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 0 deletions.
1 change: 1 addition & 0 deletions internal/filtering/filtering.go
Expand Up @@ -33,6 +33,7 @@ import (
// The IDs of built-in filter lists.
//
// Keep in sync with client/src/helpers/constants.js.
// TODO(d.kolyshev): Add RewritesListID and don't forget to keep in sync.
const (
CustomListID = -iota
SysHostsListID
Expand Down
69 changes: 69 additions & 0 deletions internal/filtering/rewrite/item.go
@@ -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
}
147 changes: 147 additions & 0 deletions internal/filtering/rewrite/storage.go
@@ -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
}
40 changes: 40 additions & 0 deletions internal/filtering/rewrite/storage_test.go
@@ -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)
}
2 changes: 2 additions & 0 deletions internal/filtering/rewrites.go
Expand Up @@ -17,6 +17,8 @@ import (
"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.
Expand Down
1 change: 1 addition & 0 deletions internal/filtering/rewrites_test.go
Expand Up @@ -10,6 +10,7 @@ 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)
Expand Down

0 comments on commit e6f8aee

Please sign in to comment.