mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-10-26 11:27:18 +00:00
filtering: imp code, docs
This commit is contained in:
parent
a7d2af91f4
commit
c4a416ee59
@ -73,7 +73,7 @@ func (m *SortedMap[K, V]) Clear() {
|
||||
return
|
||||
}
|
||||
|
||||
m.keys = nil
|
||||
m.keys = m.keys[:0]
|
||||
clear(m.vals)
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
@ -30,12 +29,9 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
|
||||
dr := nr.DNSRewrite
|
||||
if dr.NewCNAME != "" {
|
||||
// NewCNAME rules have a higher priority than other rules.
|
||||
rules = []*ResultRule{{
|
||||
// #nosec G115 -- The overflow is required for backwards
|
||||
// compatibility.
|
||||
FilterListID: rulelist.APIID(nr.GetFilterListID()),
|
||||
Text: nr.Text(),
|
||||
}}
|
||||
rules = []*ResultRule{
|
||||
NewResultRule(nr),
|
||||
}
|
||||
|
||||
return Result{
|
||||
Rules: rules,
|
||||
@ -48,21 +44,13 @@ func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) {
|
||||
case dns.RcodeSuccess:
|
||||
dnsrr.RCode = dr.RCode
|
||||
dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value)
|
||||
rules = append(rules, &ResultRule{
|
||||
// #nosec G115 -- The overflow is required for backwards
|
||||
// compatibility.
|
||||
FilterListID: rulelist.APIID(nr.GetFilterListID()),
|
||||
Text: nr.Text(),
|
||||
})
|
||||
rules = append(rules, NewResultRule(nr))
|
||||
default:
|
||||
// RcodeRefused and other such codes have higher priority. Return
|
||||
// immediately.
|
||||
rules = []*ResultRule{{
|
||||
// #nosec G115 -- The overflow is required for backwards
|
||||
// compatibility.
|
||||
FilterListID: rulelist.APIID(nr.GetFilterListID()),
|
||||
Text: nr.Text(),
|
||||
}}
|
||||
rules = []*ResultRule{
|
||||
NewResultRule(nr),
|
||||
}
|
||||
dnsrr = &DNSRewriteResult{
|
||||
RCode: dr.RCode,
|
||||
}
|
||||
|
||||
@ -306,82 +306,6 @@ type Filter struct {
|
||||
ID rules.ListID `yaml:"id"`
|
||||
}
|
||||
|
||||
// Reason holds an enum detailing why it was filtered or not filtered
|
||||
type Reason int
|
||||
|
||||
const (
|
||||
// reasons for not filtering
|
||||
|
||||
// NotFilteredNotFound - host was not find in any checks, default value for result
|
||||
NotFilteredNotFound Reason = iota
|
||||
// NotFilteredAllowList - the host is explicitly allowed
|
||||
NotFilteredAllowList
|
||||
// NotFilteredError is returned when there was an error during
|
||||
// checking. Reserved, currently unused.
|
||||
NotFilteredError
|
||||
|
||||
// reasons for filtering
|
||||
|
||||
// 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
|
||||
FilteredParental
|
||||
// FilteredInvalid - the request was invalid and was not processed
|
||||
FilteredInvalid
|
||||
// FilteredSafeSearch - the host was replaced with safesearch variant
|
||||
FilteredSafeSearch
|
||||
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
||||
FilteredBlockedService
|
||||
|
||||
// Rewritten is returned when there was a rewrite by a legacy DNS rewrite
|
||||
// rule.
|
||||
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.
|
||||
//
|
||||
// TODO(a.garipov): Remove Rewritten and RewrittenAutoHosts by merging their
|
||||
// functionality into RewrittenRule.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2499.
|
||||
RewrittenRule
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Resync with actual code names or replace completely
|
||||
// in HTTP API v1.
|
||||
var reasonNames = []string{
|
||||
NotFilteredNotFound: "NotFilteredNotFound",
|
||||
NotFilteredAllowList: "NotFilteredWhiteList",
|
||||
NotFilteredError: "NotFilteredError",
|
||||
|
||||
FilteredBlockList: "FilteredBlackList",
|
||||
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
||||
FilteredParental: "FilteredParental",
|
||||
FilteredInvalid: "FilteredInvalid",
|
||||
FilteredSafeSearch: "FilteredSafeSearch",
|
||||
FilteredBlockedService: "FilteredBlockedService",
|
||||
|
||||
Rewritten: "Rewrite",
|
||||
RewrittenAutoHosts: "RewriteEtcHosts",
|
||||
RewrittenRule: "RewriteRule",
|
||||
}
|
||||
|
||||
func (r Reason) String() string {
|
||||
if r < 0 || int(r) >= len(reasonNames) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return reasonNames[r]
|
||||
}
|
||||
|
||||
// In returns true if reasons include r.
|
||||
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }
|
||||
|
||||
// SetEnabled sets the status of the *DNSFilter.
|
||||
func (d *DNSFilter) SetEnabled(enabled bool) {
|
||||
atomic.StoreUint32(&d.conf.enabled, mathutil.BoolToNumber[uint32](enabled))
|
||||
@ -556,54 +480,6 @@ func (d *DNSFilter) ParentalBlockHost() (host string) {
|
||||
return d.conf.ParentalBlockHost
|
||||
}
|
||||
|
||||
// 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 netip.Addr `json:",omitempty"`
|
||||
|
||||
// FilterListID is the ID of the rule's filter list.
|
||||
FilterListID rulelist.APIID `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Result contains the result of a request check.
|
||||
//
|
||||
// All fields transitively have omitempty tags so that the query log doesn't
|
||||
// become too large.
|
||||
//
|
||||
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
|
||||
// a sum type or an interface?
|
||||
type Result struct {
|
||||
// DNSRewriteResult is the $dnsrewrite filter rule result.
|
||||
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
|
||||
|
||||
// CanonName is the CNAME value from the lookup rewrite result. It is empty
|
||||
// unless Reason is set to Rewritten or RewrittenRule.
|
||||
CanonName string `json:",omitempty"`
|
||||
|
||||
// ServiceName is the name of the blocked service. It is empty unless
|
||||
// Reason is set to FilteredBlockedService.
|
||||
ServiceName string `json:",omitempty"`
|
||||
|
||||
// IPList is the lookup rewrite result. It is empty unless Reason is set to
|
||||
// Rewritten.
|
||||
IPList []netip.Addr `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.
|
||||
//
|
||||
// TODO(d.kolyshev): Get rid of this flag.
|
||||
IsFiltered bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Matched returns true if any match at all was found regardless of
|
||||
// whether it was filtered or not.
|
||||
func (r Reason) Matched() bool {
|
||||
@ -1048,12 +924,7 @@ func (d *DNSFilter) matchHost(
|
||||
func makeResult(matchedRules []rules.Rule, reason Reason) (res Result) {
|
||||
resRules := make([]*ResultRule, len(matchedRules))
|
||||
for i, mr := range matchedRules {
|
||||
resRules[i] = &ResultRule{
|
||||
// #nosec G115 -- The overflow is required for backwards
|
||||
// compatibility.
|
||||
FilterListID: rulelist.APIID(mr.GetFilterListID()),
|
||||
Text: mr.Text(),
|
||||
}
|
||||
resRules[i] = NewResultRule(mr)
|
||||
}
|
||||
|
||||
return Result{
|
||||
|
||||
152
internal/filtering/result.go
Normal file
152
internal/filtering/result.go
Normal file
@ -0,0 +1,152 @@
|
||||
package filtering
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
)
|
||||
|
||||
// Result contains the result of a request check. All fields transitively have
|
||||
// omitempty tags so that the query log doesn't become too large.
|
||||
//
|
||||
// TODO(a.garipov): Clarify relationships between fields. Perhaps replace with
|
||||
// a sum type or an interface?
|
||||
type Result struct {
|
||||
// DNSRewriteResult is the $dnsrewrite filter rule result.
|
||||
DNSRewriteResult *DNSRewriteResult `json:",omitempty"`
|
||||
|
||||
// CanonName is the CNAME value from the lookup rewrite result. It is empty
|
||||
// unless Reason is set to Rewritten or RewrittenRule.
|
||||
CanonName string `json:",omitempty"`
|
||||
|
||||
// ServiceName is the name of the blocked service. It is empty unless
|
||||
// Reason is set to FilteredBlockedService.
|
||||
ServiceName string `json:",omitempty"`
|
||||
|
||||
// IPList is the lookup rewrite result. It is empty unless Reason is set to
|
||||
// Rewritten.
|
||||
IPList []netip.Addr `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.
|
||||
//
|
||||
// TODO(d.kolyshev): Get rid of this flag.
|
||||
IsFiltered bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
// 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 netip.Addr `json:",omitzero"`
|
||||
|
||||
// FilterListID is the ID of the rule's filter list.
|
||||
FilterListID rulelist.APIID `json:",omitempty"`
|
||||
}
|
||||
|
||||
// NewResultRule converts an URLFilter rule into a *ResultRule. nr must not be
|
||||
// nil.
|
||||
func NewResultRule(r rules.Rule) (rr *ResultRule) {
|
||||
return &ResultRule{
|
||||
// #nosec G115 -- The overflow is required for backwards
|
||||
// compatibility.
|
||||
FilterListID: rulelist.APIID(r.GetFilterListID()),
|
||||
Text: r.Text(),
|
||||
}
|
||||
}
|
||||
|
||||
// Reason holds an enum detailing why it was filtered or not filtered
|
||||
type Reason int
|
||||
|
||||
const (
|
||||
// NotFilteredNotFound: the host was not find in any checks, default value
|
||||
// for results.
|
||||
NotFilteredNotFound Reason = iota
|
||||
|
||||
// NotFilteredAllowList: the host is explicitly allowed.
|
||||
NotFilteredAllowList
|
||||
|
||||
// NotFilteredError is returned when there was an error during checking.
|
||||
// Reserved, currently unused.
|
||||
NotFilteredError
|
||||
|
||||
// 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.
|
||||
FilteredParental
|
||||
|
||||
// FilteredInvalid: the request was invalid and was not processed.
|
||||
FilteredInvalid
|
||||
|
||||
// FilteredSafeSearch: the host was replaced with safesearch variant.
|
||||
FilteredSafeSearch
|
||||
|
||||
// FilteredBlockedService: the host is blocked by the blocked services
|
||||
// feature.
|
||||
FilteredBlockedService
|
||||
|
||||
// Rewritten is returned when there was a rewrite by a legacy DNS rewrite
|
||||
// rule.
|
||||
Rewritten
|
||||
|
||||
// RewrittenAutoHosts is returned when there was a rewrite by /etc/hosts.
|
||||
RewrittenAutoHosts
|
||||
|
||||
// RewrittenRule is returned when a $dnsrewrite filter rule was applied.
|
||||
//
|
||||
// TODO(a.garipov): Remove [Rewritten] and [RewrittenAutoHosts] by merging
|
||||
// their functionality into RewrittenRule.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/2499.
|
||||
RewrittenRule
|
||||
)
|
||||
|
||||
// TODO(a.garipov): Resync with actual code names or replace completely in HTTP
|
||||
// API v1.
|
||||
var reasonNames = []string{
|
||||
NotFilteredNotFound: "NotFilteredNotFound",
|
||||
NotFilteredAllowList: "NotFilteredWhiteList",
|
||||
NotFilteredError: "NotFilteredError",
|
||||
|
||||
FilteredBlockList: "FilteredBlackList",
|
||||
FilteredSafeBrowsing: "FilteredSafeBrowsing",
|
||||
FilteredParental: "FilteredParental",
|
||||
FilteredInvalid: "FilteredInvalid",
|
||||
FilteredSafeSearch: "FilteredSafeSearch",
|
||||
FilteredBlockedService: "FilteredBlockedService",
|
||||
|
||||
Rewritten: "Rewrite",
|
||||
RewrittenAutoHosts: "RewriteEtcHosts",
|
||||
RewrittenRule: "RewriteRule",
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ fmt.Stringer = NotFilteredNotFound
|
||||
|
||||
// String implements the [fmt.Stringer] interface for Reason.
|
||||
func (r Reason) String() (s string) {
|
||||
if r < 0 || int(r) >= len(reasonNames) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return reasonNames[r]
|
||||
}
|
||||
|
||||
// In returns true if reasons include r.
|
||||
func (r Reason) In(reasons ...Reason) (ok bool) { return slices.Contains(reasons, r) }
|
||||
@ -155,16 +155,15 @@ func (f *Filter) setFromHTTP(
|
||||
) (parseRes *ParseResult, err error) {
|
||||
defer func() { err = errors.Annotate(err, "setting from http: %w") }()
|
||||
|
||||
text, parseRes, err := f.readFromHTTP(ctx, parseBuf, cli, cachePath, maxSize)
|
||||
data, parseRes, err := f.readFromHTTP(ctx, parseBuf, cli, cachePath, maxSize)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Add filterlist.BytesRuleList.
|
||||
f.ruleList = filterlist.NewString(&filterlist.StringConfig{
|
||||
f.ruleList = filterlist.NewBytes(&filterlist.BytesConfig{
|
||||
ID: f.urlFilterID,
|
||||
RulesText: text,
|
||||
RulesText: data,
|
||||
IgnoreCosmetic: true,
|
||||
})
|
||||
|
||||
@ -180,27 +179,27 @@ func (f *Filter) readFromHTTP(
|
||||
cli *http.Client,
|
||||
cachePath string,
|
||||
maxSize uint64,
|
||||
) (text string, parseRes *ParseResult, err error) {
|
||||
) (data []byte, parseRes *ParseResult, err error) {
|
||||
urlStr := f.url.String()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("making request for http url %q: %w", urlStr, err)
|
||||
return nil, nil, fmt.Errorf("making request for http url %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("requesting from http url: %w", err)
|
||||
return nil, nil, fmt.Errorf("requesting from http url: %w", err)
|
||||
}
|
||||
defer func() { err = errors.WithDeferred(err, resp.Body.Close()) }()
|
||||
|
||||
// TODO(a.garipov): Use [agdhttp.CheckStatus] when it's moved to golibs.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
|
||||
return nil, nil, fmt.Errorf("got status code %d, want %d", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
fltFile, err := aghrenameio.NewPendingFile(cachePath, aghos.DefaultPermFile)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("creating temp file: %w", err)
|
||||
return nil, nil, fmt.Errorf("creating temp file: %w", err)
|
||||
}
|
||||
defer func() { err = aghrenameio.WithDeferredCleanup(err, fltFile) }()
|
||||
|
||||
@ -211,10 +210,10 @@ func (f *Filter) readFromHTTP(
|
||||
httpBody := ioutil.LimitReader(resp.Body, maxSize)
|
||||
parseRes, err = parser.Parse(mw, httpBody, parseBuf)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("parsing response from http url %q: %w", urlStr, err)
|
||||
return nil, nil, fmt.Errorf("parsing response from http url %q: %w", urlStr, err)
|
||||
}
|
||||
|
||||
return buf.String(), parseRes, nil
|
||||
return buf.Bytes(), parseRes, nil
|
||||
}
|
||||
|
||||
// setName sets the title using either the already-present name, the given title
|
||||
|
||||
@ -47,9 +47,9 @@ const (
|
||||
//
|
||||
// TODO(d.kolyshev): Add URLFilterIDLegacyRewrite here and to the UI.
|
||||
const (
|
||||
IDCustom rules.ListID = 0
|
||||
IDBlockedService rules.ListID = math.MaxUint64 - 2 + 1
|
||||
IDSafeSearch rules.ListID = math.MaxUint64 - 5 + 1
|
||||
IDCustom rules.ListID = rules.ListID(APIIDCustom)
|
||||
IDBlockedService rules.ListID = math.MaxUint64 - rules.ListID(-APIIDBlockedService) + 1
|
||||
IDSafeSearch rules.ListID = math.MaxUint64 - rules.ListID(-APIIDSafeSearch) + 1
|
||||
)
|
||||
|
||||
// UID is the type for the unique IDs of filtering-rule lists.
|
||||
|
||||
@ -40,11 +40,13 @@ type StorageConfig struct {
|
||||
CacheDir string
|
||||
|
||||
// AllowFilters are the filtering-rule lists used to exclude domain names
|
||||
// from the filtering. Each item must not be nil.
|
||||
// from the filtering. Each item must not be nil and must have unique IDs
|
||||
// between AllowFilters and BlockFilters.
|
||||
AllowFilters []*Filter
|
||||
|
||||
// BlockFilters are the filtering-rule lists used to block domain names.
|
||||
// Each item must not be nil.
|
||||
// Each item must not be nil and must have unique IDs between AllowFilters
|
||||
// and BlockFilters.
|
||||
BlockFilters []*Filter
|
||||
|
||||
// CustomRules contains custom rules of the user. They have priority over
|
||||
|
||||
Loading…
Reference in New Issue
Block a user