Skip to content

Commit

Permalink
all: add $dnsrewrite handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ainar-g committed Dec 18, 2020
1 parent 49c55e3 commit e14073b
Show file tree
Hide file tree
Showing 18 changed files with 584 additions and 129 deletions.
4 changes: 2 additions & 2 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

set -e -f -u

if [ "$(git diff --cached --name-only '*.js')" ]
if [ "$(git diff --cached --name-only -- '*.js')" ]
then
make js-lint js-test
fi

if [ "$(git diff --cached --name-only '*.go')" ]
if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ]
then
make go-lint go-test
fi
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to

### Added

- `$dnsrewrite` modifier for filters ([#2102]).
- The host checking API and the query logs API can now return multiple matched
rules ([#2102]).
- Detecting of network interface configured to have static IP address via
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ GPG_KEY := [email protected]
GPG_KEY_PASSPHRASE :=
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
VERBOSE := -v
REBUILD_CLIENT = 1

# See release target
DIST_DIR=dist
Expand Down Expand Up @@ -124,7 +125,8 @@ all: build
init:
git config core.hooksPath .githooks

build: client_with_deps
build:
test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0
$(GO) mod download
PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./...
CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.14
require (
github.com/AdguardTeam/dnsproxy v0.33.7
github.com/AdguardTeam/golibs v0.4.4
github.com/AdguardTeam/urlfilter v0.13.0
github.com/AdguardTeam/urlfilter v0.14.0
github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.0.1
github.com/fsnotify/fsnotify v1.4.9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c=
github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
github.com/AdguardTeam/urlfilter v0.14.0 h1:+aAhOvZDVGzl5gTERB4pOJCL1zxMyw7vLecJJ6TQTCw=
github.com/AdguardTeam/urlfilter v0.14.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
Expand Down
145 changes: 85 additions & 60 deletions internal/dnsfilter/dnsfilter.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package dnsfilter implements a DNS filter.
// Package dnsfilter implements a DNS request and response filter.
package dnsfilter

import (
Expand Down Expand Up @@ -95,8 +95,8 @@ type filtersInitializerParams struct {
type DNSFilter struct {
rulesStorage *filterlist.RuleStorage
filteringEngine *urlfilter.DNSEngine
rulesStorageWhite *filterlist.RuleStorage
filteringEngineWhite *urlfilter.DNSEngine
rulesStorageAllow *filterlist.RuleStorage
filteringEngineAllow *urlfilter.DNSEngine
engineLock sync.RWMutex

parentalServer string // access via methods
Expand Down Expand Up @@ -127,16 +127,16 @@ const (

// NotFilteredNotFound - host was not find in any checks, default value for result
NotFilteredNotFound Reason = iota
// NotFilteredWhiteList - the host is explicitly whitelisted
NotFilteredWhiteList
// NotFilteredAllowList - the host is explicitly allowed
NotFilteredAllowList
// NotFilteredError is returned when there was an error during
// checking. Reserved, currently unused.
NotFilteredError

// reasons for filtering

// FilteredBlackList - the host was matched to be advertising host
FilteredBlackList
// FilteredBlockList - the host was matched to be advertising host
FilteredBlockList
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
FilteredSafeBrowsing
// FilteredParental - the host was matched to be outside of parental control settings
Expand All @@ -155,16 +155,20 @@ const (
// RewriteAutoHosts is returned when there was a rewrite by
// autohosts rules (/etc/hosts and so on).
RewriteAutoHosts

// DNSRewriteRule is returned when a $dnsrewrite filter rule was
// applied.
DNSRewriteRule
)

// TODO(a.garipov): Resync with actual code names or replace completely
// in HTTP API v1.
var reasonNames = []string{
NotFilteredNotFound: "NotFilteredNotFound",
NotFilteredWhiteList: "NotFilteredWhiteList",
NotFilteredAllowList: "NotFilteredWhiteList",
NotFilteredError: "NotFilteredError",

FilteredBlackList: "FilteredBlackList",
FilteredBlockList: "FilteredBlackList",
FilteredSafeBrowsing: "FilteredSafeBrowsing",
FilteredParental: "FilteredParental",
FilteredInvalid: "FilteredInvalid",
Expand All @@ -174,12 +178,15 @@ var reasonNames = []string{
ReasonRewrite: "Rewrite",

RewriteAutoHosts: "RewriteEtcHosts",

DNSRewriteRule: "DNSRewriteRule",
}

func (r Reason) String() string {
if uint(r) >= uint(len(reasonNames)) {
if r < 0 || int(r) >= len(reasonNames) {
return ""
}

return reasonNames[r]
}

Expand Down Expand Up @@ -278,16 +285,15 @@ func (d *DNSFilter) reset() {
}
}

if d.rulesStorageWhite != nil {
err = d.rulesStorageWhite.Close()
if d.rulesStorageAllow != nil {
err = d.rulesStorageAllow.Close()
if err != nil {
log.Error("dnsfilter: rulesStorageWhite.Close: %s", err)
log.Error("dnsfilter: rulesStorageAllow.Close: %s", err)
}
}
}

type dnsFilterContext struct {
stats Stats
safebrowsingCache cache.Cache
parentalCache cache.Cache
safeSearchCache cache.Cache
Expand Down Expand Up @@ -339,6 +345,9 @@ type Result struct {
// ServiceName is the name of the blocked service. It is empty
// unless Reason is set to FilteredBlockedService.
ServiceName string `json:",omitempty"`

// DNSRewriteResult is the $dnsrewrite filter rule result.
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
}

// Matched returns true if any match at all was found regardless of
Expand Down Expand Up @@ -385,7 +394,7 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering

// Then check the filter lists.
// if request is blocked -- it should be blocked.
// if it is whitelisted -- we should do nothing with it anymore.
// if it is allowlisted -- we should do nothing with it anymore.
if setts.FilteringEnabled {
result, err = d.matchHost(host, qtype, *setts)
if err != nil {
Expand Down Expand Up @@ -476,9 +485,7 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
// . repeat for the new domain name (Note: we return only the last CNAME)
// . Find A or AAAA record for a domain name (exact match or by wildcard)
// . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array
func (d *DNSFilter) processRewrites(host string, qtype uint16) Result {
var res Result

func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
d.confLock.RLock()
defer d.confLock.RUnlock()

Expand All @@ -493,7 +500,8 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) Result {
log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer)

if host == rr[0].Answer { // "host == CNAME" is an exception
res.Reason = 0
res.Reason = NotFilteredNotFound

return res
}

Expand Down Expand Up @@ -616,7 +624,7 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
if err != nil {
return err
}
rulesStorageWhite, filteringEngineWhite, err := createFilteringEngine(allowFilters)
rulesStorageAllow, filteringEngineAllow, err := createFilteringEngine(allowFilters)
if err != nil {
return err
}
Expand All @@ -625,8 +633,8 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
d.reset()
d.rulesStorage = rulesStorage
d.filteringEngine = filteringEngine
d.rulesStorageWhite = rulesStorageWhite
d.filteringEngineWhite = filteringEngineWhite
d.rulesStorageAllow = rulesStorageAllow
d.filteringEngineAllow = filteringEngineAllow
d.engineLock.Unlock()

// Make sure that the OS reclaims memory as soon as possible
Expand All @@ -636,9 +644,27 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
return nil
}

// matchHostProcessAllowList processes the allowlist logic of host
// matching.
func (d *DNSFilter) matchHostProcessAllowList(host string, dnsres urlfilter.DNSResult) (res Result) {
var rule rules.Rule
if dnsres.NetworkRule != nil {
rule = dnsres.NetworkRule
} else if dnsres.HostRulesV4 != nil {
rule = dnsres.HostRulesV4[0]
} else if dnsres.HostRulesV6 != nil {
rule = dnsres.HostRulesV6[0]
}

log.Debug("Filtering: found allowlist rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())

return makeResult(rule, NotFilteredAllowList)
}

// matchHost is a low-level way to check only if hostname is filtered by rules,
// skipping expensive safebrowsing and parental lookups.
func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) {
func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (res Result, err error) {
d.engineLock.RLock()
// Keep in mind that this lock must be held no just when calling Match()
// but also while using the rules returned by it.
Expand All @@ -652,77 +678,76 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS
DNSType: qtype,
}

if d.filteringEngineWhite != nil {
rr, ok := d.filteringEngineWhite.MatchRequest(ureq)
if d.filteringEngineAllow != nil {
dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq)
if ok {
var rule rules.Rule
if rr.NetworkRule != nil {
rule = rr.NetworkRule
} else if rr.HostRulesV4 != nil {
rule = rr.HostRulesV4[0]
} else if rr.HostRulesV6 != nil {
rule = rr.HostRulesV6[0]
}

log.Debug("Filtering: found whitelist rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, NotFilteredWhiteList)
return res, nil
return d.matchHostProcessAllowList(host, dnsres), nil
}
}

if d.filteringEngine == nil {
return Result{}, nil
}

rr, ok := d.filteringEngine.MatchRequest(ureq)
if !ok {
dnsres, ok := d.filteringEngine.MatchRequest(ureq)

// Check DNS rewrites first, because the API there is a bit
// awkward.
if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 {
res = d.processDNSRewrites(dnsr)
if res.Reason == DNSRewriteRule && res.CanonName == host {
// A rewrite of a host to itself. Go on and
// try matching other things.
} else {
return res, nil
}
} else if !ok {
return Result{}, nil
}

if rr.NetworkRule != nil {
if dnsres.NetworkRule != nil {
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID())
reason := FilteredBlackList
if rr.NetworkRule.Whitelist {
reason = NotFilteredWhiteList
host, dnsres.NetworkRule.Text(), dnsres.NetworkRule.GetFilterListID())
reason := FilteredBlockList
if dnsres.NetworkRule.Whitelist {
reason = NotFilteredAllowList
}
res := makeResult(rr.NetworkRule, reason)
return res, nil

return makeResult(dnsres.NetworkRule, reason), nil
}

if qtype == dns.TypeA && rr.HostRulesV4 != nil {
rule := rr.HostRulesV4[0] // note that we process only 1 matched rule
if qtype == dns.TypeA && dnsres.HostRulesV4 != nil {
rule := dnsres.HostRulesV4[0] // note that we process only 1 matched rule
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, FilteredBlackList)
res = makeResult(rule, FilteredBlockList)
res.Rules[0].IP = rule.IP.To4()

return res, nil
}

if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil {
rule := rr.HostRulesV6[0] // note that we process only 1 matched rule
if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil {
rule := dnsres.HostRulesV6[0] // note that we process only 1 matched rule
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, FilteredBlackList)
res = makeResult(rule, FilteredBlockList)
res.Rules[0].IP = rule.IP

return res, nil
}

if rr.HostRulesV4 != nil || rr.HostRulesV6 != nil {
if dnsres.HostRulesV4 != nil || dnsres.HostRulesV6 != nil {
// Question Type doesn't match the host rules
// Return the first matched host rule, but without an IP address
var rule rules.Rule
if rr.HostRulesV4 != nil {
rule = rr.HostRulesV4[0]
} else if rr.HostRulesV6 != nil {
rule = rr.HostRulesV6[0]
if dnsres.HostRulesV4 != nil {
rule = dnsres.HostRulesV4[0]
} else if dnsres.HostRulesV6 != nil {
rule = dnsres.HostRulesV6[0]
}
log.Debug("Filtering: found rule for host %q: %q list_id: %d",
host, rule.Text(), rule.GetFilterListID())
res := makeResult(rule, FilteredBlackList)
res = makeResult(rule, FilteredBlockList)
res.Rules[0].IP = net.IP{}

return res, nil
Expand All @@ -741,7 +766,7 @@ func makeResult(rule rules.Rule, reason Reason) Result {
}},
}

if reason == FilteredBlackList {
if reason == FilteredBlockList {
res.IsFiltered = true
}

Expand Down
Loading

0 comments on commit e14073b

Please sign in to comment.