From e81a9c7d5686dfab00c7d3d23d58ae13db1964a4 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Tue, 23 Jul 2019 12:21:37 +0300 Subject: [PATCH] + dnsfilter: use global and per-client BlockedServices array --- dnsfilter/dnsfilter.go | 42 +++++++++++++++++++++- dnsfilter/dnsfilter_test.go | 16 +++++++++ dnsforward/dnsforward.go | 4 +++ dnsforward/querylog.go | 4 +++ home/blocked_services.go | 70 +++++++++++++++++++++++++++++++++++++ home/dns.go | 23 ++++++++++-- home/home.go | 1 + 7 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 home/blocked_services.go diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go index 20d17493..cc724ff7 100644 --- a/dnsfilter/dnsfilter.go +++ b/dnsfilter/dnsfilter.go @@ -39,12 +39,19 @@ const defaultParentalURL = "%s://%s/check-parental-control-hash?prefixes=%s&sens const defaultParentalSensitivity = 13 // use "TEEN" by default const maxDialCacheSize = 2 // the number of host names for safebrowsing and parental control +// ServiceEntry - blocked service array element +type ServiceEntry struct { + Name string + Rules []*urlfilter.NetworkRule +} + // RequestFilteringSettings is custom filtering settings type RequestFilteringSettings struct { FilteringEnabled bool SafeSearchEnabled bool SafeBrowsingEnabled bool ParentalEnabled bool + ServicesRules []ServiceEntry } // RewriteEntry is a rewrite array element @@ -139,6 +146,8 @@ const ( FilteredInvalid // FilteredSafeSearch - the host was replaced with safesearch variant FilteredSafeSearch + // FilteredBlockedService - the host is blocked by "blocked services" settings + FilteredBlockedService // ReasonRewrite - rewrite rule was applied ReasonRewrite @@ -155,6 +164,7 @@ func (i Reason) String() string { "FilteredParental", "FilteredInvalid", "FilteredSafeSearch", + "FilteredBlockedService", "Rewrite", } @@ -185,6 +195,9 @@ type Result struct { // for ReasonRewrite: CanonName string `json:",omitempty"` // CNAME value IPList []net.IP `json:",omitempty"` // list of IP addresses + + // for FilteredBlockedService: + ServiceName string `json:",omitempty"` // Name of the blocked service } // Matched can be used to see if any match at all was found, no matter filtered or not @@ -209,7 +222,7 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, clientAddr string) (Res setts.SafeSearchEnabled = d.SafeSearchEnabled setts.SafeBrowsingEnabled = d.SafeBrowsingEnabled setts.ParentalEnabled = d.ParentalEnabled - if len(clientAddr) != 0 && d.FilterHandler != nil { + if d.FilterHandler != nil { d.FilterHandler(clientAddr, &setts) } @@ -232,6 +245,13 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, clientAddr string) (Res } } + if len(setts.ServicesRules) != 0 { + result = matchBlockedServicesRules(host, setts.ServicesRules) + if result.Reason.Matched() { + return result, nil + } + } + // check safeSearch if no match if setts.SafeSearchEnabled { result, err = d.checkSafeSearch(host) @@ -326,6 +346,26 @@ func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result { return res } +func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result { + req := urlfilter.NewRequestForHostname(host) + res := Result{} + + for _, s := range svcs { + for _, rule := range s.Rules { + if rule.Match(req) { + res.Reason = FilteredBlockedService + res.IsFiltered = true + res.ServiceName = s.Name + res.Rule = rule.Text() + log.Debug("Blocked Services: matched rule: %s host: %s service: %s", + res.Rule, host, s.Name) + return res + } + } + } + return res +} + func setCacheResult(cache *fastcache.Cache, host string, res Result) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) diff --git a/dnsfilter/dnsfilter_test.go b/dnsfilter/dnsfilter_test.go index 7b78b7a5..7df5fe09 100644 --- a/dnsfilter/dnsfilter_test.go +++ b/dnsfilter/dnsfilter_test.go @@ -10,8 +10,10 @@ import ( "testing" "time" + "github.com/AdguardTeam/urlfilter" "github.com/bluele/gcache" "github.com/miekg/dns" + "github.com/stretchr/testify/assert" ) // HELPERS @@ -453,6 +455,12 @@ func applyClientSettings(clientAddr string, setts *RequestFilteringSettings) { setts.FilteringEnabled = false setts.ParentalEnabled = false setts.SafeBrowsingEnabled = true + + rule, _ := urlfilter.NewNetworkRule("||facebook.com^", 0) + s := ServiceEntry{} + s.Name = "facebook" + s.Rules = []*urlfilter.NetworkRule{rule} + setts.ServicesRules = append(setts.ServicesRules, s) } // Check behaviour without any per-client settings, @@ -485,6 +493,10 @@ func TestClientSettings(t *testing.T) { t.Fatalf("CheckHost safesearch") } + // not blocked + r, _ = d.CheckHost("facebook.com", dns.TypeA, "1.1.1.1") + assert.True(t, !r.IsFiltered) + // override client settings: d.FilterHandler = applyClientSettings @@ -505,6 +517,10 @@ func TestClientSettings(t *testing.T) { if !r.IsFiltered || r.Reason != FilteredSafeBrowsing { t.Fatalf("CheckHost FilteredSafeBrowsing") } + + // blocked by additional rules + r, _ = d.CheckHost("facebook.com", dns.TypeA, "1.1.1.1") + assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService) } // BENCHMARKS diff --git a/dnsforward/dnsforward.go b/dnsforward/dnsforward.go index 1661f68b..d09bf7be 100644 --- a/dnsforward/dnsforward.go +++ b/dnsforward/dnsforward.go @@ -96,6 +96,10 @@ type FilteringConfig struct { ParentalBlockHost string `yaml:"parental_block_host"` SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"` + // Names of services to block (globally). + // Per-client settings can override this configuration. + BlockedServices []string `json:"blocked_services"` + dnsfilter.Config `yaml:",inline"` } diff --git a/dnsforward/querylog.go b/dnsforward/querylog.go index aac4b25a..230cffd2 100644 --- a/dnsforward/querylog.go +++ b/dnsforward/querylog.go @@ -186,6 +186,10 @@ func (l *queryLog) getQueryLog() []map[string]interface{} { jsonEntry["filterId"] = entry.Result.FilterID } + if len(entry.Result.ServiceName) != 0 { + jsonEntry["service_name"] = entry.Result.ServiceName + } + answers := answerToMap(a) if answers != nil { jsonEntry["answer"] = answers diff --git a/home/blocked_services.go b/home/blocked_services.go new file mode 100644 index 00000000..7707e122 --- /dev/null +++ b/home/blocked_services.go @@ -0,0 +1,70 @@ +package home + +import ( + "github.com/AdguardTeam/AdGuardHome/dnsfilter" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/urlfilter" +) + +var serviceRules map[string][]*urlfilter.NetworkRule // service name -> filtering rules + +type svc struct { + name string + rules []string +} + +// Keep in sync with: +// client/src/helpers/constants.js +// client/src/components/ui/Icons.js +var serviceRulesArray = []svc{ + {"whatsapp", []string{"||whatsapp.net^"}}, + {"facebook", []string{"||facebook.com^"}}, + {"twitter", []string{"||twitter.com^", "||t.co^", "||twimg.com^"}}, + {"youtube", []string{"||youtube.com^", "||ytimg.com^"}}, + {"messenger", []string{"||fb.com^", "||facebook.com^"}}, + {"twitch", []string{"||twitch.tv^", "||ttvnw.net^"}}, + {"netflix", []string{"||nflxext.com^", "||netflix.com^"}}, + {"instagram", []string{"||instagram.com^"}}, + {"snapchat", []string{"||snapchat.com^"}}, + {"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^"}}, + {"ok", []string{"||ok.ru^"}}, + {"skype", []string{"||skype.com^"}}, + {"vk", []string{"||vk.com^"}}, + {"steam", []string{"||steam.com^"}}, + {"mail_ru", []string{"||mail.ru^"}}, +} + +// convert array to map +func initServices() { + serviceRules = make(map[string][]*urlfilter.NetworkRule) + for _, s := range serviceRulesArray { + rules := []*urlfilter.NetworkRule{} + for _, text := range s.rules { + rule, err := urlfilter.NewNetworkRule(text, 0) + if err != nil { + log.Error("urlfilter.NewNetworkRule: %s rule: %s", err, text) + continue + } + rules = append(rules, rule) + } + serviceRules[s.name] = rules + } +} + +// ApplyBlockedServices - set blocked services settings for this DNS request +func ApplyBlockedServices(setts *dnsfilter.RequestFilteringSettings, list []string) { + setts.ServicesRules = []dnsfilter.ServiceEntry{} + for _, name := range list { + rules, ok := serviceRules[name] + + if !ok { + log.Error("unknown service name: %s", name) + continue + } + + s := dnsfilter.ServiceEntry{} + s.Name = name + s.Rules = rules + setts.ServicesRules = append(setts.ServicesRules, s) + } +} diff --git a/home/dns.go b/home/dns.go index b195bd40..b474bc5d 100644 --- a/home/dns.go +++ b/home/dns.go @@ -54,6 +54,7 @@ func initDNSServer(baseDir string) { log.Error("upstream.AddressToUpstream: %s", err) return } + config.dnsctx.rdnsIP = make(map[string]bool) config.dnsctx.rdnsChannel = make(chan string, 256) go asyncRDNSLoop() @@ -210,19 +211,35 @@ func generateServerConfig() (dnsforward.ServerConfig, error) { newconfig.Upstreams = upstreamConfig.Upstreams newconfig.DomainsReservedUpstreams = upstreamConfig.DomainReservedUpstreams newconfig.AllServers = config.DNS.AllServers - newconfig.FilterHandler = applyClientSettings + newconfig.FilterHandler = applyAdditionalFiltering newconfig.OnDNSRequest = onDNSRequest return newconfig, nil } // If a client has his own settings, apply them -func applyClientSettings(clientAddr string, setts *dnsfilter.RequestFilteringSettings) { +func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteringSettings) { + + ApplyBlockedServices(setts, config.DNS.BlockedServices) + + if len(clientAddr) == 0 { + return + } + c, ok := config.clients.Find(clientAddr) - if !ok || !c.UseOwnSettings { + if !ok { return } log.Debug("Using settings for client with IP %s", clientAddr) + + if c.UseOwnBlockedServices { + ApplyBlockedServices(setts, c.BlockedServices) + } + + if !c.UseOwnSettings { + return + } + setts.FilteringEnabled = c.FilteringEnabled setts.SafeSearchEnabled = c.SafeSearchEnabled setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled diff --git a/home/home.go b/home/home.go index 4d711559..3064ad80 100644 --- a/home/home.go +++ b/home/home.go @@ -101,6 +101,7 @@ func run(args options) { initConfig() config.clients.Init() + initServices() if !config.firstRun { // Do the upgrade if necessary