2021-05-21 16:15:47 +03:00
|
|
|
// Package filtering implements a DNS request and response filter.
|
|
|
|
package filtering
|
2018-08-30 17:25:33 +03:00
|
|
|
|
|
|
|
import (
|
2021-01-29 16:09:31 +03:00
|
|
|
"context"
|
2018-08-30 17:25:33 +03:00
|
|
|
"fmt"
|
2021-11-26 18:25:43 +03:00
|
|
|
"io/fs"
|
2018-10-29 15:46:58 +03:00
|
|
|
"net"
|
2022-09-23 13:23:35 +03:00
|
|
|
"net/http"
|
2019-07-05 17:35:40 +03:00
|
|
|
"os"
|
2022-09-23 13:23:35 +03:00
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2019-10-22 14:58:20 +03:00
|
|
|
"runtime"
|
2020-05-13 00:46:35 +03:00
|
|
|
"runtime/debug"
|
2018-08-30 17:25:33 +03:00
|
|
|
"strings"
|
2019-10-09 19:51:26 +03:00
|
|
|
"sync"
|
2021-05-24 14:48:42 +03:00
|
|
|
"sync/atomic"
|
2019-06-18 16:18:13 +03:00
|
|
|
|
2022-08-04 19:05:28 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
|
2021-04-12 18:31:45 +03:00
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
2019-04-18 14:31:13 +03:00
|
|
|
"github.com/AdguardTeam/dnsproxy/upstream"
|
2019-08-22 15:09:43 +03:00
|
|
|
"github.com/AdguardTeam/golibs/cache"
|
2021-11-26 18:25:43 +03:00
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
2019-02-25 16:44:22 +03:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2023-01-26 17:13:18 +03:00
|
|
|
"github.com/AdguardTeam/golibs/mathutil"
|
2021-07-29 17:40:31 +03:00
|
|
|
"github.com/AdguardTeam/golibs/stringutil"
|
2019-05-15 16:46:11 +03:00
|
|
|
"github.com/AdguardTeam/urlfilter"
|
2019-11-27 15:11:46 +03:00
|
|
|
"github.com/AdguardTeam/urlfilter/filterlist"
|
|
|
|
"github.com/AdguardTeam/urlfilter/rules"
|
2019-05-22 12:38:17 +03:00
|
|
|
"github.com/miekg/dns"
|
2022-09-23 13:23:35 +03:00
|
|
|
"golang.org/x/exp/slices"
|
2018-08-30 17:25:33 +03:00
|
|
|
)
|
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
// The IDs of built-in filter lists.
|
|
|
|
//
|
2021-12-27 19:40:39 +03:00
|
|
|
// Keep in sync with client/src/helpers/constants.js.
|
2022-11-28 14:19:56 +03:00
|
|
|
// TODO(d.kolyshev): Add RewritesListID and don't forget to keep in sync.
|
2021-11-26 18:25:43 +03:00
|
|
|
const (
|
|
|
|
CustomListID = -iota
|
|
|
|
SysHostsListID
|
|
|
|
BlockedSvcsListID
|
|
|
|
ParentalListID
|
|
|
|
SafeBrowsingListID
|
|
|
|
SafeSearchListID
|
|
|
|
)
|
|
|
|
|
2019-07-23 12:21:37 +03:00
|
|
|
// ServiceEntry - blocked service array element
|
|
|
|
type ServiceEntry struct {
|
|
|
|
Name string
|
2019-11-27 15:11:46 +03:00
|
|
|
Rules []*rules.NetworkRule
|
2019-07-23 12:21:37 +03:00
|
|
|
}
|
|
|
|
|
2021-05-21 16:15:47 +03:00
|
|
|
// Settings are custom filtering settings for a client.
|
|
|
|
type Settings struct {
|
2020-06-23 14:36:26 +03:00
|
|
|
ClientName string
|
2021-01-20 17:27:53 +03:00
|
|
|
ClientIP net.IP
|
2020-06-23 14:36:26 +03:00
|
|
|
ClientTags []string
|
|
|
|
|
|
|
|
ServicesRules []ServiceEntry
|
2021-03-25 20:30:30 +03:00
|
|
|
|
2021-10-20 19:52:13 +03:00
|
|
|
ProtectionEnabled bool
|
2021-03-25 20:30:30 +03:00
|
|
|
FilteringEnabled bool
|
|
|
|
SafeSearchEnabled bool
|
|
|
|
SafeBrowsingEnabled bool
|
|
|
|
ParentalEnabled bool
|
2023-03-15 14:31:07 +03:00
|
|
|
|
|
|
|
// ClientSafeSearch is a client configured safe search.
|
|
|
|
ClientSafeSearch SafeSearch
|
2019-05-28 14:14:12 +03:00
|
|
|
}
|
|
|
|
|
2021-02-04 20:35:13 +03:00
|
|
|
// Resolver is the interface for net.Resolver to simplify testing.
|
|
|
|
type Resolver interface {
|
2021-03-11 12:17:54 +03:00
|
|
|
LookupIP(ctx context.Context, network, host string) (ips []net.IP, err error)
|
2021-02-04 20:35:13 +03:00
|
|
|
}
|
|
|
|
|
2018-11-30 13:32:51 +03:00
|
|
|
// Config allows you to configure DNS filtering with New() or just change variables directly.
|
|
|
|
type Config struct {
|
2021-05-24 14:48:42 +03:00
|
|
|
// enabled is used to be returned within Settings.
|
|
|
|
//
|
|
|
|
// It is of type uint32 to be accessed by atomic.
|
2022-09-23 13:23:35 +03:00
|
|
|
//
|
|
|
|
// TODO(e.burkov): Use atomic.Bool in Go 1.19.
|
2021-05-24 14:48:42 +03:00
|
|
|
enabled uint32
|
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
|
|
|
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
|
|
|
|
|
2021-03-23 12:32:07 +03:00
|
|
|
ParentalEnabled bool `yaml:"parental_enabled"`
|
|
|
|
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
2019-05-28 14:14:12 +03:00
|
|
|
|
2019-08-22 15:09:43 +03:00
|
|
|
SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes)
|
|
|
|
SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes)
|
|
|
|
ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes)
|
2023-03-15 14:31:07 +03:00
|
|
|
// TODO(a.garipov): Use timeutil.Duration
|
|
|
|
CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes)
|
|
|
|
|
|
|
|
SafeSearchConf SafeSearchConfig `yaml:"safe_search"`
|
|
|
|
SafeSearch SafeSearch `yaml:"-"`
|
2019-08-22 15:09:43 +03:00
|
|
|
|
2021-12-27 19:40:39 +03:00
|
|
|
Rewrites []*LegacyRewrite `yaml:"rewrites"`
|
2019-07-29 11:37:16 +03:00
|
|
|
|
2020-02-18 20:17:35 +03:00
|
|
|
// Names of services to block (globally).
|
|
|
|
// Per-client settings can override this configuration.
|
|
|
|
BlockedServices []string `yaml:"blocked_services"`
|
|
|
|
|
2021-04-12 18:31:45 +03:00
|
|
|
// EtcHosts is a container of IP-hostname pairs taken from the operating
|
|
|
|
// system configuration files (e.g. /etc/hosts).
|
2021-10-14 19:39:21 +03:00
|
|
|
EtcHosts *aghnet.HostsContainer `yaml:"-"`
|
2020-03-20 15:05:43 +03:00
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
// Called when the configuration is changed by HTTP request
|
|
|
|
ConfigModified func() `yaml:"-"`
|
2018-11-30 13:32:51 +03:00
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
// Register an HTTP handler
|
2022-08-04 19:05:28 +03:00
|
|
|
HTTPRegister aghhttp.RegisterFunc `yaml:"-"`
|
2021-02-04 20:35:13 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
// HTTPClient is the client to use for updating the remote filters.
|
|
|
|
HTTPClient *http.Client `yaml:"-"`
|
|
|
|
|
|
|
|
// DataDir is used to store filters' contents.
|
|
|
|
DataDir string `yaml:"-"`
|
|
|
|
|
|
|
|
// filtersMu protects filter lists.
|
|
|
|
filtersMu *sync.RWMutex
|
|
|
|
|
|
|
|
// Filters are the blocking filter lists.
|
|
|
|
Filters []FilterYAML `yaml:"-"`
|
|
|
|
|
|
|
|
// WhitelistFilters are the allowing filter lists.
|
|
|
|
WhitelistFilters []FilterYAML `yaml:"-"`
|
|
|
|
|
|
|
|
// UserRules is the global list of custom rules.
|
|
|
|
UserRules []string `yaml:"-"`
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2018-09-14 16:50:56 +03:00
|
|
|
// LookupStats store stats collected during safebrowsing or parental checks
|
2018-08-30 17:25:33 +03:00
|
|
|
type LookupStats struct {
|
|
|
|
Requests uint64 // number of HTTP requests that were sent
|
|
|
|
CacheHits uint64 // number of lookups that didn't need HTTP requests
|
|
|
|
Pending int64 // number of currently pending HTTP requests
|
|
|
|
PendingMax int64 // maximum number of pending HTTP requests
|
|
|
|
}
|
|
|
|
|
2019-02-22 16:34:36 +03:00
|
|
|
// Stats store LookupStats for safebrowsing, parental and safesearch
|
2018-08-30 17:25:33 +03:00
|
|
|
type Stats struct {
|
|
|
|
Safebrowsing LookupStats
|
|
|
|
Parental LookupStats
|
2019-02-22 16:34:36 +03:00
|
|
|
Safesearch LookupStats
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
// Parameters to pass to filters-initializer goroutine
|
|
|
|
type filtersInitializerParams struct {
|
2020-02-26 19:58:25 +03:00
|
|
|
allowFilters []Filter
|
|
|
|
blockFilters []Filter
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
type hostChecker struct {
|
2021-05-21 16:15:47 +03:00
|
|
|
check func(host string, qtype uint16, setts *Settings) (res Result, err error)
|
2021-03-25 20:30:30 +03:00
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// DNSFilter matches hostnames and DNS requests against filtering rules.
|
|
|
|
type DNSFilter struct {
|
2022-09-23 13:23:35 +03:00
|
|
|
rulesStorage *filterlist.RuleStorage
|
|
|
|
filteringEngine *urlfilter.DNSEngine
|
|
|
|
|
2020-12-21 17:48:07 +03:00
|
|
|
rulesStorageAllow *filterlist.RuleStorage
|
|
|
|
filteringEngineAllow *urlfilter.DNSEngine
|
2022-09-23 13:23:35 +03:00
|
|
|
|
|
|
|
engineLock sync.RWMutex
|
2019-05-15 16:46:11 +03:00
|
|
|
|
2019-10-16 12:57:49 +03:00
|
|
|
parentalServer string // access via methods
|
|
|
|
safeBrowsingServer string // access via methods
|
|
|
|
parentalUpstream upstream.Upstream
|
|
|
|
safeBrowsingUpstream upstream.Upstream
|
2019-10-09 19:51:26 +03:00
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
safebrowsingCache cache.Cache
|
|
|
|
parentalCache cache.Cache
|
|
|
|
|
2021-05-24 14:48:42 +03:00
|
|
|
Config // for direct access by library users, even a = assignment
|
|
|
|
// confLock protects Config.
|
2019-10-09 19:51:26 +03:00
|
|
|
confLock sync.RWMutex
|
|
|
|
|
|
|
|
// Channel for passing data to filters-initializer goroutine
|
|
|
|
filtersInitializerChan chan filtersInitializerParams
|
|
|
|
filtersInitializerLock sync.Mutex
|
2021-01-29 16:09:31 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
refreshLock *sync.Mutex
|
|
|
|
|
|
|
|
// filterTitleRegexp is the regular expression to retrieve a name of a
|
|
|
|
// filter list.
|
2022-12-20 16:40:42 +03:00
|
|
|
//
|
|
|
|
// TODO(e.burkov): Don't use regexp for such a simple text processing task.
|
2022-09-23 13:23:35 +03:00
|
|
|
filterTitleRegexp *regexp.Regexp
|
|
|
|
|
2023-03-15 14:31:07 +03:00
|
|
|
safeSearch SafeSearch
|
2021-03-25 20:30:30 +03:00
|
|
|
hostCheckers []hostChecker
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2019-01-24 20:11:01 +03:00
|
|
|
// Filter represents a filter list
|
2018-11-30 13:24:42 +03:00
|
|
|
type Filter struct {
|
2021-12-27 19:40:39 +03:00
|
|
|
// FilePath is the path to a filtering rules list file.
|
|
|
|
FilePath string `yaml:"-"`
|
|
|
|
|
|
|
|
// Data is the content of the file.
|
|
|
|
Data []byte `yaml:"-"`
|
|
|
|
|
|
|
|
// ID is automatically assigned when filter is added using nextFilterID.
|
2022-09-23 13:23:35 +03:00
|
|
|
ID int64 `yaml:"id"`
|
2018-11-30 13:24:42 +03:00
|
|
|
}
|
|
|
|
|
2018-09-14 16:50:56 +03:00
|
|
|
// Reason holds an enum detailing why it was filtered or not filtered
|
2018-08-30 17:25:33 +03:00
|
|
|
type Reason int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// reasons for not filtering
|
2019-01-24 20:11:01 +03:00
|
|
|
|
|
|
|
// NotFilteredNotFound - host was not find in any checks, default value for result
|
|
|
|
NotFilteredNotFound Reason = iota
|
2020-12-21 17:48:07 +03:00
|
|
|
// NotFilteredAllowList - the host is explicitly allowed
|
|
|
|
NotFilteredAllowList
|
2020-12-17 13:32:46 +03:00
|
|
|
// NotFilteredError is returned when there was an error during
|
2020-12-14 20:12:57 +03:00
|
|
|
// checking. Reserved, currently unused.
|
|
|
|
NotFilteredError
|
2018-08-30 17:25:33 +03:00
|
|
|
|
|
|
|
// reasons for filtering
|
2019-01-24 20:11:01 +03:00
|
|
|
|
2020-12-21 17:48:07 +03:00
|
|
|
// FilteredBlockList - the host was matched to be advertising host
|
|
|
|
FilteredBlockList
|
2019-01-24 20:11:01 +03:00
|
|
|
// FilteredSafeBrowsing - the host was matched to be malicious/phishing
|
|
|
|
FilteredSafeBrowsing
|
|
|
|
// FilteredParental - the host was matched to be outside of parental control settings
|
|
|
|
FilteredParental
|
|
|
|
// FilteredInvalid - the request was invalid and was not processed
|
|
|
|
FilteredInvalid
|
|
|
|
// FilteredSafeSearch - the host was replaced with safesearch variant
|
|
|
|
FilteredSafeSearch
|
2019-07-23 12:21:37 +03:00
|
|
|
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
|
|
|
FilteredBlockedService
|
2019-07-29 11:37:16 +03:00
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
// Rewritten is returned when there was a rewrite by a legacy DNS rewrite
|
|
|
|
// rule.
|
2020-12-28 18:41:50 +03:00
|
|
|
Rewritten
|
|
|
|
|
|
|
|
// RewrittenAutoHosts is returned when there was a rewrite by autohosts
|
|
|
|
// rules (/etc/hosts and so on).
|
|
|
|
RewrittenAutoHosts
|
|
|
|
|
|
|
|
// RewrittenRule is returned when a $dnsrewrite filter rule was applied.
|
|
|
|
//
|
2021-10-14 19:39:21 +03:00
|
|
|
// TODO(a.garipov): Remove Rewritten and RewrittenAutoHosts by merging their
|
|
|
|
// functionality into RewrittenRule.
|
2020-12-28 18:41:50 +03:00
|
|
|
//
|
|
|
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/2499.
|
|
|
|
RewrittenRule
|
2018-08-30 17:25:33 +03:00
|
|
|
)
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// TODO(a.garipov): Resync with actual code names or replace completely
|
|
|
|
// in HTTP API v1.
|
2019-10-09 19:51:26 +03:00
|
|
|
var reasonNames = []string{
|
2020-12-17 13:32:46 +03:00
|
|
|
NotFilteredNotFound: "NotFilteredNotFound",
|
2020-12-21 17:48:07 +03:00
|
|
|
NotFilteredAllowList: "NotFilteredWhiteList",
|
2020-12-17 13:32:46 +03:00
|
|
|
NotFilteredError: "NotFilteredError",
|
2019-10-09 19:51:26 +03:00
|
|
|
|
2020-12-21 17:48:07 +03:00
|
|
|
FilteredBlockList: "FilteredBlackList",
|
2020-12-17 13:32:46 +03:00
|
|
|
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
|
|
|
FilteredParental: "FilteredParental",
|
|
|
|
FilteredInvalid: "FilteredInvalid",
|
|
|
|
FilteredSafeSearch: "FilteredSafeSearch",
|
|
|
|
FilteredBlockedService: "FilteredBlockedService",
|
2019-10-09 19:51:26 +03:00
|
|
|
|
2020-12-28 18:41:50 +03:00
|
|
|
Rewritten: "Rewrite",
|
|
|
|
RewrittenAutoHosts: "RewriteEtcHosts",
|
|
|
|
RewrittenRule: "RewriteRule",
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
|
|
|
|
2019-08-20 00:55:32 +03:00
|
|
|
func (r Reason) String() string {
|
2020-12-21 17:48:07 +03:00
|
|
|
if r < 0 || int(r) >= len(reasonNames) {
|
2019-10-09 19:51:26 +03:00
|
|
|
return ""
|
|
|
|
}
|
2020-12-21 17:48:07 +03:00
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
return reasonNames[r]
|
|
|
|
}
|
|
|
|
|
2020-11-20 17:32:41 +03:00
|
|
|
// In returns true if reasons include r.
|
2022-09-23 13:23:35 +03:00
|
|
|
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }
|
2020-11-20 17:32:41 +03:00
|
|
|
|
2021-05-24 14:48:42 +03:00
|
|
|
// SetEnabled sets the status of the *DNSFilter.
|
|
|
|
func (d *DNSFilter) SetEnabled(enabled bool) {
|
2023-01-26 17:13:18 +03:00
|
|
|
atomic.StoreUint32(&d.enabled, mathutil.BoolToNumber[uint32](enabled))
|
2021-05-24 14:48:42 +03:00
|
|
|
}
|
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
// GetConfig - get configuration
|
2021-05-24 14:48:42 +03:00
|
|
|
func (d *DNSFilter) GetConfig() (s Settings) {
|
|
|
|
d.confLock.RLock()
|
|
|
|
defer d.confLock.RUnlock()
|
|
|
|
|
|
|
|
return Settings{
|
2021-10-20 19:52:13 +03:00
|
|
|
FilteringEnabled: atomic.LoadUint32(&d.Config.enabled) != 0,
|
2023-03-15 14:31:07 +03:00
|
|
|
SafeSearchEnabled: d.Config.SafeSearchConf.Enabled,
|
2021-05-24 14:48:42 +03:00
|
|
|
SafeBrowsingEnabled: d.Config.SafeBrowsingEnabled,
|
|
|
|
ParentalEnabled: d.Config.ParentalEnabled,
|
|
|
|
}
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteDiskConfig - write configuration
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) WriteDiskConfig(c *Config) {
|
2022-09-23 13:23:35 +03:00
|
|
|
func() {
|
|
|
|
d.confLock.Lock()
|
|
|
|
defer d.confLock.Unlock()
|
2021-09-17 14:37:55 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
*c = d.Config
|
|
|
|
c.Rewrites = cloneRewrites(c.Rewrites)
|
|
|
|
}()
|
|
|
|
|
|
|
|
d.filtersMu.RLock()
|
|
|
|
defer d.filtersMu.RUnlock()
|
|
|
|
|
|
|
|
c.Filters = slices.Clone(d.Filters)
|
|
|
|
c.WhitelistFilters = slices.Clone(d.WhitelistFilters)
|
|
|
|
c.UserRules = slices.Clone(d.UserRules)
|
2021-09-17 14:37:55 +03:00
|
|
|
}
|
|
|
|
|
2021-12-27 19:40:39 +03:00
|
|
|
// cloneRewrites returns a deep copy of entries.
|
|
|
|
func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
|
|
|
|
clone = make([]*LegacyRewrite, len(entries))
|
|
|
|
for i, rw := range entries {
|
|
|
|
clone[i] = rw.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
return clone
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
2019-07-29 11:37:16 +03:00
|
|
|
|
2022-08-25 18:44:19 +03:00
|
|
|
// SetFilters sets new filters, synchronously or asynchronously. When filters
|
|
|
|
// are set asynchronously, the old filters continue working until the new
|
|
|
|
// filters are ready.
|
|
|
|
//
|
|
|
|
// In this case the caller must ensure that the old filter files are intact.
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error {
|
2019-10-09 19:51:26 +03:00
|
|
|
if async {
|
|
|
|
params := filtersInitializerParams{
|
2020-02-26 19:58:25 +03:00
|
|
|
allowFilters: allowFilters,
|
|
|
|
blockFilters: blockFilters,
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
2019-07-29 11:37:16 +03:00
|
|
|
|
2022-10-21 20:14:43 +03:00
|
|
|
d.filtersInitializerLock.Lock()
|
2022-09-23 13:23:35 +03:00
|
|
|
defer d.filtersInitializerLock.Unlock()
|
|
|
|
|
2022-10-21 20:14:43 +03:00
|
|
|
// Remove all pending tasks.
|
|
|
|
removeLoop:
|
|
|
|
for {
|
2019-10-09 19:51:26 +03:00
|
|
|
select {
|
|
|
|
case <-d.filtersInitializerChan:
|
2022-10-21 20:14:43 +03:00
|
|
|
// Continue removing.
|
2019-10-09 19:51:26 +03:00
|
|
|
default:
|
2022-10-21 20:14:43 +03:00
|
|
|
break removeLoop
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d.filtersInitializerChan <- params
|
2022-10-21 20:14:43 +03:00
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
return nil
|
2019-07-23 11:43:30 +03:00
|
|
|
}
|
2019-10-09 19:51:26 +03:00
|
|
|
|
2020-02-26 19:58:25 +03:00
|
|
|
err := d.initFiltering(allowFilters, blockFilters)
|
2019-10-09 19:51:26 +03:00
|
|
|
if err != nil {
|
2022-10-21 20:14:43 +03:00
|
|
|
log.Error("filtering: can't initialize filtering subsystem: %s", err)
|
|
|
|
|
2019-10-09 19:51:26 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts initializing new filters by signal from channel
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) filtersInitializer() {
|
2019-10-09 19:51:26 +03:00
|
|
|
for {
|
|
|
|
params := <-d.filtersInitializerChan
|
2020-02-26 19:58:25 +03:00
|
|
|
err := d.initFiltering(params.allowFilters, params.blockFilters)
|
2019-10-09 19:51:26 +03:00
|
|
|
if err != nil {
|
|
|
|
log.Error("Can't initialize filtering subsystem: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close - close the object
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) Close() {
|
2020-03-05 13:12:21 +03:00
|
|
|
d.engineLock.Lock()
|
|
|
|
defer d.engineLock.Unlock()
|
2022-09-23 13:23:35 +03:00
|
|
|
|
2020-02-26 19:58:25 +03:00
|
|
|
d.reset()
|
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) reset() {
|
2019-10-09 19:51:26 +03:00
|
|
|
if d.rulesStorage != nil {
|
2022-09-23 13:23:35 +03:00
|
|
|
if err := d.rulesStorage.Close(); err != nil {
|
2021-05-21 16:15:47 +03:00
|
|
|
log.Error("filtering: rulesStorage.Close: %s", err)
|
2020-12-07 15:38:05 +03:00
|
|
|
}
|
2019-07-23 11:43:30 +03:00
|
|
|
}
|
2020-12-07 15:38:05 +03:00
|
|
|
|
2020-12-21 17:48:07 +03:00
|
|
|
if d.rulesStorageAllow != nil {
|
2022-09-23 13:23:35 +03:00
|
|
|
if err := d.rulesStorageAllow.Close(); err != nil {
|
2021-05-21 16:15:47 +03:00
|
|
|
log.Error("filtering: rulesStorageAllow.Close: %s", err)
|
2020-12-07 15:38:05 +03:00
|
|
|
}
|
2020-02-26 19:58:25 +03:00
|
|
|
}
|
2019-07-23 11:43:30 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// ResultRule contains information about applied rules.
|
|
|
|
type ResultRule struct {
|
|
|
|
// Text is the text of the rule.
|
|
|
|
Text string `json:",omitempty"`
|
|
|
|
// IP is the host IP. It is nil unless the rule uses the
|
|
|
|
// /etc/hosts syntax or the reason is FilteredSafeSearch.
|
|
|
|
IP net.IP `json:",omitempty"`
|
2021-06-17 14:09:16 +03:00
|
|
|
// FilterListID is the ID of the rule's filter list.
|
|
|
|
FilterListID int64 `json:",omitempty"`
|
2020-12-17 13:32:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Result contains the result of a request check.
|
|
|
|
//
|
2023-02-28 16:34:11 +03:00
|
|
|
// All fields transitively have omitempty tags so that the query log doesn't
|
|
|
|
// become too large.
|
2020-12-17 13:32:46 +03:00
|
|
|
//
|
2023-02-28 16:34:11 +03:00
|
|
|
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
|
|
|
|
// a sum type or an interface?
|
2018-08-30 17:25:33 +03:00
|
|
|
type Result struct {
|
2022-09-02 14:52:19 +03:00
|
|
|
// DNSRewriteResult is the $dnsrewrite filter rule result.
|
|
|
|
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
|
2020-12-17 13:32:46 +03:00
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
// CanonName is the CNAME value from the lookup rewrite result. It is empty
|
|
|
|
// unless Reason is set to Rewritten or RewrittenRule.
|
2020-12-17 13:32:46 +03:00
|
|
|
CanonName string `json:",omitempty"`
|
2019-07-23 12:21:37 +03:00
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
// ServiceName is the name of the blocked service. It is empty unless
|
|
|
|
// Reason is set to FilteredBlockedService.
|
2020-12-17 13:32:46 +03:00
|
|
|
ServiceName string `json:",omitempty"`
|
2020-12-21 17:48:07 +03:00
|
|
|
|
2022-09-02 14:52:19 +03:00
|
|
|
// IPList is the lookup rewrite result. It is empty unless Reason is set to
|
|
|
|
// Rewritten.
|
|
|
|
IPList []net.IP `json:",omitempty"`
|
|
|
|
|
|
|
|
// Rules are applied rules. If Rules are not empty, each rule is not nil.
|
|
|
|
Rules []*ResultRule `json:",omitempty"`
|
|
|
|
|
|
|
|
// Reason is the reason for blocking or unblocking the request.
|
|
|
|
Reason Reason `json:",omitempty"`
|
|
|
|
|
|
|
|
// IsFiltered is true if the request is filtered.
|
|
|
|
IsFiltered bool `json:",omitempty"`
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// Matched returns true if any match at all was found regardless of
|
|
|
|
// whether it was filtered or not.
|
2018-08-30 17:25:33 +03:00
|
|
|
func (r Reason) Matched() bool {
|
|
|
|
return r != NotFilteredNotFound
|
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// CheckHostRules tries to match the host against filtering rules only.
|
2022-02-03 21:19:32 +03:00
|
|
|
func (d *DNSFilter) CheckHostRules(host string, rrtype uint16, setts *Settings) (Result, error) {
|
|
|
|
return d.matchHost(strings.ToLower(host), rrtype, setts)
|
2019-12-23 15:59:49 +03:00
|
|
|
}
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
// CheckHost tries to match the host against filtering rules, then safebrowsing
|
|
|
|
// and parental control rules, if they are enabled.
|
|
|
|
func (d *DNSFilter) CheckHost(
|
|
|
|
host string,
|
|
|
|
qtype uint16,
|
2021-05-21 16:15:47 +03:00
|
|
|
setts *Settings,
|
2021-03-25 20:30:30 +03:00
|
|
|
) (res Result, err error) {
|
|
|
|
// Sometimes clients try to resolve ".", which is a request to get root
|
|
|
|
// servers.
|
2018-08-30 17:25:33 +03:00
|
|
|
if host == "" {
|
2021-10-20 19:52:13 +03:00
|
|
|
return Result{}, nil
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
2020-03-20 15:05:43 +03:00
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
host = strings.ToLower(host)
|
2019-02-22 16:34:36 +03:00
|
|
|
|
2021-10-20 19:52:13 +03:00
|
|
|
if setts.FilteringEnabled {
|
|
|
|
res = d.processRewrites(host, qtype)
|
|
|
|
if res.Reason == Rewritten {
|
|
|
|
return res, nil
|
|
|
|
}
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
for _, hc := range d.hostCheckers {
|
|
|
|
res, err = hc.check(host, qtype, setts)
|
2018-08-30 17:25:33 +03:00
|
|
|
if err != nil {
|
2021-03-25 20:30:30 +03:00
|
|
|
return Result{}, fmt.Errorf("%s: %w", hc.name, err)
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
2020-09-11 15:52:46 +03:00
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
if res.Reason.Matched() {
|
|
|
|
return res, nil
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
// matchSysHosts tries to match the host against the operating system's hosts
|
2021-11-16 16:16:38 +03:00
|
|
|
// database. err is always nil.
|
2021-11-11 16:19:33 +03:00
|
|
|
func (d *DNSFilter) matchSysHosts(
|
|
|
|
host string,
|
|
|
|
qtype uint16,
|
|
|
|
setts *Settings,
|
|
|
|
) (res Result, err error) {
|
2021-10-20 19:52:13 +03:00
|
|
|
if !setts.FilteringEnabled || d.EtcHosts == nil {
|
2021-11-16 16:16:38 +03:00
|
|
|
return res, nil
|
2021-03-25 20:30:30 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 18:16:33 +03:00
|
|
|
dnsres, _ := d.EtcHosts.MatchRequest(&urlfilter.DNSRequest{
|
2021-10-14 19:39:21 +03:00
|
|
|
Hostname: host,
|
|
|
|
SortedClientTags: setts.ClientTags,
|
2021-11-16 16:16:38 +03:00
|
|
|
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
2021-10-14 19:39:21 +03:00
|
|
|
ClientIP: setts.ClientIP.String(),
|
|
|
|
ClientName: setts.ClientName,
|
|
|
|
DNSType: qtype,
|
|
|
|
})
|
2021-11-11 16:19:33 +03:00
|
|
|
if dnsres == nil {
|
2021-11-16 16:16:38 +03:00
|
|
|
return res, nil
|
2020-11-06 17:34:40 +03:00
|
|
|
}
|
|
|
|
|
2021-11-23 18:01:48 +03:00
|
|
|
dnsr := dnsres.DNSRewrites()
|
|
|
|
if len(dnsr) == 0 {
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
res = d.processDNSRewrites(dnsr)
|
|
|
|
res.Reason = RewrittenAutoHosts
|
|
|
|
for _, r := range res.Rules {
|
|
|
|
r.Text = stringutil.Coalesce(d.EtcHosts.Translate(r.Text), r.Text)
|
2020-11-06 17:34:40 +03:00
|
|
|
}
|
|
|
|
|
2021-11-16 16:16:38 +03:00
|
|
|
return res, nil
|
2020-11-06 17:34:40 +03:00
|
|
|
}
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
// processRewrites performs filtering based on the legacy rewrite records.
|
|
|
|
//
|
|
|
|
// Firstly, it finds CNAME rewrites for host. If the CNAME is the same as host,
|
|
|
|
// this query isn't filtered. If it's different, repeat the process for the new
|
|
|
|
// CNAME, breaking loops in the process.
|
|
|
|
//
|
|
|
|
// Secondly, it finds A or AAAA rewrites for host and, if found, sets res.IPList
|
|
|
|
// accordingly. If the found rewrite has a special value of "A" or "AAAA", the
|
|
|
|
// result is an exception.
|
2020-12-21 17:48:07 +03:00
|
|
|
func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
|
2019-10-09 19:51:26 +03:00
|
|
|
d.confLock.RLock()
|
|
|
|
defer d.confLock.RUnlock()
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
rewrites, matched := findRewrites(d.Rewrites, host, qtype)
|
|
|
|
if !matched {
|
|
|
|
return Result{}
|
2019-07-29 11:37:16 +03:00
|
|
|
}
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
res.Reason = Rewritten
|
|
|
|
|
2021-07-29 17:40:31 +03:00
|
|
|
cnames := stringutil.NewSet()
|
2020-01-16 12:51:35 +03:00
|
|
|
origHost := host
|
2021-12-24 20:14:36 +03:00
|
|
|
for matched && len(rewrites) > 0 && rewrites[0].Type == dns.TypeCNAME {
|
2021-12-27 19:40:39 +03:00
|
|
|
rw := rewrites[0]
|
|
|
|
rwPat := rw.Domain
|
|
|
|
rwAns := rw.Answer
|
2020-05-26 11:42:42 +03:00
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
log.Debug("rewrite: cname for %s is %s", host, rwAns)
|
|
|
|
|
2021-12-27 19:40:39 +03:00
|
|
|
if origHost == rwAns || rwPat == rwAns {
|
|
|
|
// Either a request for the hostname itself or a rewrite of
|
|
|
|
// a pattern onto itself, both of which are an exception rules.
|
|
|
|
// Return a not filtered result.
|
|
|
|
return Result{}
|
|
|
|
} else if host == rwAns && isWildcard(rwPat) {
|
|
|
|
// An "*.example.com → sub.example.com" rewrite matching in a loop.
|
|
|
|
//
|
|
|
|
// See https://github.com/AdguardTeam/AdGuardHome/issues/4016.
|
2020-12-21 17:48:07 +03:00
|
|
|
|
2021-12-27 19:40:39 +03:00
|
|
|
res.CanonName = host
|
|
|
|
|
|
|
|
break
|
2020-05-26 11:42:42 +03:00
|
|
|
}
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
host = rwAns
|
2021-04-20 16:26:19 +03:00
|
|
|
if cnames.Has(host) {
|
2021-12-24 20:14:36 +03:00
|
|
|
log.Info("rewrite: cname loop for %q on %q", origHost, host)
|
2021-04-20 16:26:19 +03:00
|
|
|
|
2020-01-16 12:51:35 +03:00
|
|
|
return res
|
2019-07-29 11:37:16 +03:00
|
|
|
}
|
2021-04-20 16:26:19 +03:00
|
|
|
|
|
|
|
cnames.Add(host)
|
2021-12-24 20:14:36 +03:00
|
|
|
res.CanonName = host
|
|
|
|
rewrites, matched = findRewrites(d.Rewrites, host, qtype)
|
2019-07-29 11:37:16 +03:00
|
|
|
}
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
setRewriteResult(&res, host, rewrites, qtype)
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// setRewriteResult sets the Reason or IPList of res if necessary. res must not
|
|
|
|
// be nil.
|
2021-12-27 19:40:39 +03:00
|
|
|
func setRewriteResult(res *Result, host string, rewrites []*LegacyRewrite, qtype uint16) {
|
2021-12-24 20:14:36 +03:00
|
|
|
for _, rw := range rewrites {
|
|
|
|
if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) {
|
|
|
|
if rw.IP == nil {
|
|
|
|
// "A"/"AAAA" exception: allow getting from upstream.
|
2021-07-12 21:10:39 +03:00
|
|
|
res.Reason = NotFilteredNotFound
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
return
|
2020-06-03 12:04:23 +03:00
|
|
|
}
|
|
|
|
|
2021-12-24 20:14:36 +03:00
|
|
|
res.IPList = append(res.IPList, rw.IP)
|
|
|
|
|
|
|
|
log.Debug("rewrite: a/aaaa for %s is %s", host, rw.IP)
|
2020-01-16 12:51:35 +03:00
|
|
|
}
|
2019-07-29 11:37:16 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
// matchBlockedServicesRules checks the host against the blocked services rules
|
|
|
|
// in settings, if any. The err is always nil, it is only there to make this
|
|
|
|
// a valid hostChecker function.
|
|
|
|
func matchBlockedServicesRules(
|
|
|
|
host string,
|
|
|
|
_ uint16,
|
2021-05-21 16:15:47 +03:00
|
|
|
setts *Settings,
|
2021-03-25 20:30:30 +03:00
|
|
|
) (res Result, err error) {
|
2021-10-20 19:52:13 +03:00
|
|
|
if !setts.ProtectionEnabled {
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
svcs := setts.ServicesRules
|
|
|
|
if len(svcs) == 0 {
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
2019-07-23 12:21:37 +03:00
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
req := rules.NewRequestForHostname(host)
|
2019-07-23 12:21:37 +03:00
|
|
|
for _, s := range svcs {
|
|
|
|
for _, rule := range s.Rules {
|
|
|
|
if rule.Match(req) {
|
|
|
|
res.Reason = FilteredBlockedService
|
|
|
|
res.IsFiltered = true
|
|
|
|
res.ServiceName = s.Name
|
2020-12-17 13:32:46 +03:00
|
|
|
|
|
|
|
ruleText := rule.Text()
|
|
|
|
res.Rules = []*ResultRule{{
|
|
|
|
FilterListID: int64(rule.GetFilterListID()),
|
|
|
|
Text: ruleText,
|
|
|
|
}}
|
|
|
|
|
|
|
|
log.Debug("blocked services: matched rule: %s host: %s service: %s",
|
|
|
|
ruleText, host, s.Name)
|
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
return res, nil
|
2019-07-23 12:21:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-25 20:30:30 +03:00
|
|
|
|
|
|
|
return res, nil
|
2019-07-23 12:21:37 +03:00
|
|
|
}
|
|
|
|
|
2018-08-30 17:25:33 +03:00
|
|
|
//
|
|
|
|
// Adding rule and matching against the rules
|
|
|
|
//
|
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
func newRuleStorage(filters []Filter) (rs *filterlist.RuleStorage, err error) {
|
|
|
|
lists := make([]filterlist.RuleList, 0, len(filters))
|
2020-02-26 19:58:25 +03:00
|
|
|
for _, f := range filters {
|
2021-11-26 18:25:43 +03:00
|
|
|
switch id := int(f.ID); {
|
|
|
|
case len(f.Data) != 0:
|
|
|
|
lists = append(lists, &filterlist.StringRuleList{
|
|
|
|
ID: id,
|
2020-02-26 19:58:25 +03:00
|
|
|
RulesText: string(f.Data),
|
2019-10-22 14:58:20 +03:00
|
|
|
IgnoreCosmetic: true,
|
2021-11-26 18:25:43 +03:00
|
|
|
})
|
|
|
|
case f.FilePath == "":
|
|
|
|
continue
|
|
|
|
case runtime.GOOS == "windows":
|
|
|
|
// On Windows we don't pass a file to urlfilter because it's
|
|
|
|
// difficult to update this file while it's being used.
|
|
|
|
var data []byte
|
|
|
|
data, err = os.ReadFile(f.FilePath)
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, fmt.Errorf("reading filter content: %w", err)
|
2019-10-22 14:58:20 +03:00
|
|
|
}
|
2021-05-21 14:55:42 +03:00
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
lists = append(lists, &filterlist.StringRuleList{
|
|
|
|
ID: id,
|
2019-10-22 14:58:20 +03:00
|
|
|
RulesText: string(data),
|
|
|
|
IgnoreCosmetic: true,
|
2021-11-26 18:25:43 +03:00
|
|
|
})
|
|
|
|
default:
|
|
|
|
var list *filterlist.FileRuleList
|
|
|
|
list, err = filterlist.NewFileRuleList(id, f.FilePath, true)
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating file rule list with %q: %w", f.FilePath, err)
|
2019-07-05 17:35:40 +03:00
|
|
|
}
|
2021-11-26 18:25:43 +03:00
|
|
|
|
|
|
|
lists = append(lists, list)
|
2019-07-04 14:00:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
rs, err = filterlist.NewRuleStorage(lists)
|
2019-05-15 16:46:11 +03:00
|
|
|
if err != nil {
|
2021-12-10 17:20:15 +03:00
|
|
|
return nil, fmt.Errorf("creating rule storage: %w", err)
|
2019-05-15 16:46:11 +03:00
|
|
|
}
|
2021-11-26 18:25:43 +03:00
|
|
|
|
|
|
|
return rs, nil
|
2020-02-26 19:58:25 +03:00
|
|
|
}
|
2019-10-09 19:51:26 +03:00
|
|
|
|
2020-11-06 12:15:08 +03:00
|
|
|
// Initialize urlfilter objects.
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error {
|
2021-11-26 18:25:43 +03:00
|
|
|
rulesStorage, err := newRuleStorage(blockFilters)
|
2020-02-26 19:58:25 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-26 18:25:43 +03:00
|
|
|
|
|
|
|
rulesStorageAllow, err := newRuleStorage(allowFilters)
|
2020-02-26 19:58:25 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-10-09 19:51:26 +03:00
|
|
|
}
|
2020-08-26 18:58:21 +03:00
|
|
|
|
2021-11-26 18:25:43 +03:00
|
|
|
filteringEngine := urlfilter.NewDNSEngine(rulesStorage)
|
|
|
|
filteringEngineAllow := urlfilter.NewDNSEngine(rulesStorageAllow)
|
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
func() {
|
|
|
|
d.engineLock.Lock()
|
|
|
|
defer d.engineLock.Unlock()
|
|
|
|
|
|
|
|
d.reset()
|
|
|
|
d.rulesStorage = rulesStorage
|
|
|
|
d.filteringEngine = filteringEngine
|
|
|
|
d.rulesStorageAllow = rulesStorageAllow
|
|
|
|
d.filteringEngineAllow = filteringEngineAllow
|
|
|
|
}()
|
2020-05-13 00:46:35 +03:00
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
// Make sure that the OS reclaims memory as soon as possible.
|
2020-05-13 00:46:35 +03:00
|
|
|
debug.FreeOSMemory()
|
2019-10-09 19:51:26 +03:00
|
|
|
log.Debug("initialized filtering engine")
|
|
|
|
|
2018-11-30 13:48:53 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
// hostRules is a helper that converts a slice of host rules into a slice of the
|
|
|
|
// rules.Rule interface values.
|
|
|
|
func hostRulesToRules(netRules []*rules.HostRule) (res []rules.Rule) {
|
|
|
|
if netRules == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
res = make([]rules.Rule, len(netRules))
|
|
|
|
for i, nr := range netRules {
|
|
|
|
res[i] = nr
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2022-02-03 21:19:32 +03:00
|
|
|
// matchHostProcessAllowList processes the allowlist logic of host matching.
|
2021-06-17 14:09:16 +03:00
|
|
|
func (d *DNSFilter) matchHostProcessAllowList(
|
|
|
|
host string,
|
2021-11-11 16:19:33 +03:00
|
|
|
dnsres *urlfilter.DNSResult,
|
2021-06-17 14:09:16 +03:00
|
|
|
) (res Result, err error) {
|
|
|
|
var matchedRules []rules.Rule
|
2020-12-21 17:48:07 +03:00
|
|
|
if dnsres.NetworkRule != nil {
|
2021-06-17 14:09:16 +03:00
|
|
|
matchedRules = []rules.Rule{dnsres.NetworkRule}
|
2020-12-21 17:48:07 +03:00
|
|
|
} else if len(dnsres.HostRulesV4) > 0 {
|
2021-06-17 14:09:16 +03:00
|
|
|
matchedRules = hostRulesToRules(dnsres.HostRulesV4)
|
2020-12-21 17:48:07 +03:00
|
|
|
} else if len(dnsres.HostRulesV6) > 0 {
|
2021-06-17 14:09:16 +03:00
|
|
|
matchedRules = hostRulesToRules(dnsres.HostRulesV6)
|
2020-12-21 17:48:07 +03:00
|
|
|
}
|
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
if len(matchedRules) == 0 {
|
2020-12-21 17:48:07 +03:00
|
|
|
return Result{}, fmt.Errorf("invalid dns result: rules are empty")
|
|
|
|
}
|
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
log.Debug("filtering: allowlist rules for host %q: %+v", host, matchedRules)
|
2020-12-21 17:48:07 +03:00
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
return makeResult(matchedRules, NotFilteredAllowList), nil
|
2020-12-21 17:48:07 +03:00
|
|
|
}
|
|
|
|
|
2021-03-26 13:03:36 +03:00
|
|
|
// matchHostProcessDNSResult processes the matched DNS filtering result.
|
|
|
|
func (d *DNSFilter) matchHostProcessDNSResult(
|
|
|
|
qtype uint16,
|
2021-11-11 16:19:33 +03:00
|
|
|
dnsres *urlfilter.DNSResult,
|
2021-03-26 13:03:36 +03:00
|
|
|
) (res Result) {
|
|
|
|
if dnsres.NetworkRule != nil {
|
|
|
|
reason := FilteredBlockList
|
|
|
|
if dnsres.NetworkRule.Whitelist {
|
|
|
|
reason = NotFilteredAllowList
|
|
|
|
}
|
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
return makeResult([]rules.Rule{dnsres.NetworkRule}, reason)
|
2021-03-26 13:03:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if qtype == dns.TypeA && dnsres.HostRulesV4 != nil {
|
2021-06-17 14:09:16 +03:00
|
|
|
res = makeResult(hostRulesToRules(dnsres.HostRulesV4), FilteredBlockList)
|
|
|
|
for i, hr := range dnsres.HostRulesV4 {
|
|
|
|
res.Rules[i].IP = hr.IP.To4()
|
|
|
|
}
|
2021-03-26 13:03:36 +03:00
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil {
|
2021-06-17 14:09:16 +03:00
|
|
|
res = makeResult(hostRulesToRules(dnsres.HostRulesV6), FilteredBlockList)
|
|
|
|
for i, hr := range dnsres.HostRulesV6 {
|
|
|
|
res.Rules[i].IP = hr.IP.To16()
|
|
|
|
}
|
2021-03-26 13:03:36 +03:00
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2023-02-28 16:34:11 +03:00
|
|
|
return hostResultForOtherQType(dnsres)
|
|
|
|
}
|
2021-03-26 13:03:36 +03:00
|
|
|
|
2023-02-28 16:34:11 +03:00
|
|
|
// hostResultForOtherQType returns a result based on the host rules in dnsres,
|
|
|
|
// if any. dnsres.HostRulesV4 take precedence over dnsres.HostRulesV6.
|
|
|
|
func hostResultForOtherQType(dnsres *urlfilter.DNSResult) (res Result) {
|
|
|
|
if len(dnsres.HostRulesV4) != 0 {
|
|
|
|
return makeResult([]rules.Rule{dnsres.HostRulesV4[0]}, FilteredBlockList)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dnsres.HostRulesV6) != 0 {
|
|
|
|
return makeResult([]rules.Rule{dnsres.HostRulesV6[0]}, FilteredBlockList)
|
2021-03-26 13:03:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return Result{}
|
|
|
|
}
|
|
|
|
|
2022-02-03 21:19:32 +03:00
|
|
|
// matchHost is a low-level way to check only if host is filtered by rules,
|
2020-11-06 12:15:08 +03:00
|
|
|
// skipping expensive safebrowsing and parental lookups.
|
2021-03-25 20:30:30 +03:00
|
|
|
func (d *DNSFilter) matchHost(
|
|
|
|
host string,
|
2022-02-03 21:19:32 +03:00
|
|
|
rrtype uint16,
|
2021-05-21 16:15:47 +03:00
|
|
|
setts *Settings,
|
2021-03-25 20:30:30 +03:00
|
|
|
) (res Result, err error) {
|
|
|
|
if !setts.FilteringEnabled {
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2023-02-28 16:34:11 +03:00
|
|
|
ufReq := &urlfilter.DNSRequest{
|
2020-11-24 19:55:05 +03:00
|
|
|
Hostname: host,
|
|
|
|
SortedClientTags: setts.ClientTags,
|
2021-01-20 17:27:53 +03:00
|
|
|
// TODO(e.burkov): Wait for urlfilter update to pass net.IP.
|
|
|
|
ClientIP: setts.ClientIP.String(),
|
|
|
|
ClientName: setts.ClientName,
|
2022-02-03 21:19:32 +03:00
|
|
|
DNSType: rrtype,
|
2020-11-24 19:55:05 +03:00
|
|
|
}
|
2020-06-23 14:36:26 +03:00
|
|
|
|
2021-10-14 19:39:21 +03:00
|
|
|
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.
|
|
|
|
//
|
|
|
|
// TODO(e.burkov): Inspect if the above is true.
|
|
|
|
defer d.engineLock.RUnlock()
|
|
|
|
|
2021-10-20 19:52:13 +03:00
|
|
|
if setts.ProtectionEnabled && d.filteringEngineAllow != nil {
|
2023-02-28 16:34:11 +03:00
|
|
|
dnsres, ok := d.filteringEngineAllow.MatchRequest(ufReq)
|
2020-02-26 19:58:25 +03:00
|
|
|
if ok {
|
2020-12-21 17:48:07 +03:00
|
|
|
return d.matchHostProcessAllowList(host, dnsres)
|
2020-02-26 19:58:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-15 16:46:11 +03:00
|
|
|
if d.filteringEngine == nil {
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2023-02-28 16:34:11 +03:00
|
|
|
dnsres, matchedEngine := d.filteringEngine.MatchRequest(ufReq)
|
|
|
|
|
2021-03-26 13:03:36 +03:00
|
|
|
// Check DNS rewrites first, because the API there is a bit awkward.
|
2023-02-28 16:34:11 +03:00
|
|
|
dnsRWRes := d.processDNSResultRewrites(dnsres, host)
|
|
|
|
if dnsRWRes.Reason != NotFilteredNotFound {
|
|
|
|
return dnsRWRes, nil
|
|
|
|
} else if !matchedEngine {
|
2019-05-15 16:46:11 +03:00
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2021-10-20 19:52:13 +03:00
|
|
|
if !setts.ProtectionEnabled {
|
|
|
|
// Don't check non-dnsrewrite filtering results.
|
|
|
|
return Result{}, nil
|
|
|
|
}
|
|
|
|
|
2022-02-03 21:19:32 +03:00
|
|
|
res = d.matchHostProcessDNSResult(rrtype, dnsres)
|
2021-06-17 14:09:16 +03:00
|
|
|
for _, r := range res.Rules {
|
2021-03-26 13:03:36 +03:00
|
|
|
log.Debug(
|
|
|
|
"filtering: found rule %q for host %q, filter list id: %d",
|
|
|
|
r.Text,
|
|
|
|
host,
|
|
|
|
r.FilterListID,
|
|
|
|
)
|
2020-01-30 19:06:09 +03:00
|
|
|
}
|
2019-05-15 16:46:11 +03:00
|
|
|
|
2021-03-26 13:03:36 +03:00
|
|
|
return res, nil
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
|
|
|
|
2023-02-28 16:34:11 +03:00
|
|
|
// processDNSResultRewrites returns an empty Result if there are no dnsrewrite
|
|
|
|
// rules in dnsres. Otherwise, it returns the processed Result.
|
|
|
|
func (d *DNSFilter) processDNSResultRewrites(
|
|
|
|
dnsres *urlfilter.DNSResult,
|
|
|
|
host string,
|
|
|
|
) (dnsRWRes Result) {
|
|
|
|
dnsr := dnsres.DNSRewrites()
|
|
|
|
if len(dnsr) == 0 {
|
|
|
|
return Result{}
|
|
|
|
}
|
|
|
|
|
|
|
|
res := d.processDNSRewrites(dnsr)
|
|
|
|
if res.Reason == RewrittenRule && res.CanonName == host {
|
|
|
|
// A rewrite of a host to itself. Go on and try matching other things.
|
|
|
|
return Result{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2020-12-17 13:32:46 +03:00
|
|
|
// makeResult returns a properly constructed Result.
|
2021-06-17 14:09:16 +03:00
|
|
|
func makeResult(matchedRules []rules.Rule, reason Reason) (res Result) {
|
|
|
|
resRules := make([]*ResultRule, len(matchedRules))
|
|
|
|
for i, mr := range matchedRules {
|
|
|
|
resRules[i] = &ResultRule{
|
|
|
|
FilterListID: int64(mr.GetFilterListID()),
|
|
|
|
Text: mr.Text(),
|
|
|
|
}
|
2020-12-17 13:32:46 +03:00
|
|
|
}
|
|
|
|
|
2021-06-17 14:09:16 +03:00
|
|
|
return Result{
|
|
|
|
Rules: resRules,
|
2022-09-02 14:52:19 +03:00
|
|
|
Reason: reason,
|
|
|
|
IsFiltered: reason == FilteredBlockList,
|
2020-02-26 19:58:25 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-16 20:54:59 +03:00
|
|
|
// InitModule manually initializes blocked services map.
|
2020-04-27 13:21:16 +03:00
|
|
|
func InitModule() {
|
|
|
|
initBlockedServices()
|
|
|
|
}
|
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
// New creates properly initialized DNS Filter that is ready to be used. c must
|
|
|
|
// be non-nil.
|
|
|
|
func New(c *Config, blockFilters []Filter) (d *DNSFilter, err error) {
|
2021-11-26 18:25:43 +03:00
|
|
|
d = &DNSFilter{
|
2022-09-23 13:23:35 +03:00
|
|
|
refreshLock: &sync.Mutex{},
|
|
|
|
filterTitleRegexp: regexp.MustCompile(`^! Title: +(.*)$`),
|
|
|
|
}
|
|
|
|
|
|
|
|
d.safebrowsingCache = cache.New(cache.Config{
|
|
|
|
EnableLRU: true,
|
|
|
|
MaxSize: c.SafeBrowsingCacheSize,
|
|
|
|
})
|
|
|
|
d.parentalCache = cache.New(cache.Config{
|
|
|
|
EnableLRU: true,
|
|
|
|
MaxSize: c.ParentalCacheSize,
|
|
|
|
})
|
|
|
|
|
2023-03-15 14:31:07 +03:00
|
|
|
d.safeSearch = c.SafeSearch
|
2019-06-24 19:00:03 +03:00
|
|
|
|
2021-03-25 20:30:30 +03:00
|
|
|
d.hostCheckers = []hostChecker{{
|
2021-10-14 19:39:21 +03:00
|
|
|
check: d.matchSysHosts,
|
|
|
|
name: "hosts container",
|
2021-03-25 20:30:30 +03:00
|
|
|
}, {
|
|
|
|
check: d.matchHost,
|
|
|
|
name: "filtering",
|
|
|
|
}, {
|
|
|
|
check: matchBlockedServicesRules,
|
|
|
|
name: "blocked services",
|
|
|
|
}, {
|
|
|
|
check: d.checkSafeBrowsing,
|
|
|
|
name: "safe browsing",
|
|
|
|
}, {
|
|
|
|
check: d.checkParental,
|
|
|
|
name: "parental",
|
|
|
|
}, {
|
|
|
|
check: d.checkSafeSearch,
|
|
|
|
name: "safe search",
|
|
|
|
}}
|
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
defer func() { err = errors.Annotate(err, "filtering: %w") }()
|
2021-12-27 19:40:39 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
err = d.initSecurityServices()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("initializing services: %s", err)
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|
2019-10-16 12:57:49 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
d.Config = *c
|
|
|
|
d.filtersMu = &sync.RWMutex{}
|
2021-12-27 19:40:39 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
err = d.prepareRewrites()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("rewrites: preparing: %s", err)
|
2018-11-30 13:47:26 +03:00
|
|
|
}
|
2018-09-10 20:34:42 +03:00
|
|
|
|
2020-02-18 20:17:35 +03:00
|
|
|
bsvcs := []string{}
|
|
|
|
for _, s := range d.BlockedServices {
|
|
|
|
if !BlockedSvcKnown(s) {
|
2020-11-05 15:20:57 +03:00
|
|
|
log.Debug("skipping unknown blocked-service %q", s)
|
2022-09-23 13:23:35 +03:00
|
|
|
|
2020-02-18 20:17:35 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
bsvcs = append(bsvcs, s)
|
|
|
|
}
|
|
|
|
d.BlockedServices = bsvcs
|
|
|
|
|
2020-02-26 19:58:25 +03:00
|
|
|
if blockFilters != nil {
|
2021-03-11 20:36:54 +03:00
|
|
|
err = d.initFiltering(nil, blockFilters)
|
2019-05-15 16:46:11 +03:00
|
|
|
if err != nil {
|
2019-10-09 19:51:26 +03:00
|
|
|
d.Close()
|
2022-09-23 13:23:35 +03:00
|
|
|
|
|
|
|
return nil, fmt.Errorf("initializing filtering subsystem: %s", err)
|
2019-05-15 16:46:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
_ = os.MkdirAll(filepath.Join(d.DataDir, filterDir), 0o755)
|
|
|
|
|
|
|
|
d.loadFilters(d.Filters)
|
|
|
|
d.loadFilters(d.WhitelistFilters)
|
|
|
|
|
|
|
|
d.Filters = deduplicateFilters(d.Filters)
|
|
|
|
d.WhitelistFilters = deduplicateFilters(d.WhitelistFilters)
|
|
|
|
|
|
|
|
updateUniqueFilterID(d.Filters)
|
|
|
|
updateUniqueFilterID(d.WhitelistFilters)
|
|
|
|
|
|
|
|
return d, nil
|
2020-01-16 14:25:40 +03:00
|
|
|
}
|
|
|
|
|
2020-01-30 19:06:09 +03:00
|
|
|
// Start - start the module:
|
|
|
|
// . start async filtering initializer goroutine
|
|
|
|
// . register web handlers
|
2020-12-17 13:32:46 +03:00
|
|
|
func (d *DNSFilter) Start() {
|
2019-10-09 19:51:26 +03:00
|
|
|
d.filtersInitializerChan = make(chan filtersInitializerParams, 1)
|
|
|
|
go d.filtersInitializer()
|
2018-08-30 17:25:33 +03:00
|
|
|
|
2022-09-23 13:23:35 +03:00
|
|
|
d.RegisterFilteringHandlers()
|
|
|
|
|
|
|
|
// Here we should start updating filters,
|
|
|
|
// but currently we can't wake up the periodic task to do so.
|
|
|
|
// So for now we just start this periodic task from here.
|
|
|
|
go d.periodicallyRefreshFilters()
|
2018-08-30 17:25:33 +03:00
|
|
|
}
|