badguardhome/dnsfilter/dnsfilter_test.go

540 lines
16 KiB
Go
Raw Normal View History

2018-08-30 14:25:33 +00:00
package dnsfilter
import (
"fmt"
2019-02-22 13:34:36 +00:00
"net"
"path"
"runtime"
2018-08-30 14:25:33 +00:00
"testing"
2019-05-22 15:30:27 +00:00
"github.com/AdguardTeam/urlfilter"
2019-05-22 15:30:27 +00:00
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
2018-08-30 14:25:33 +00:00
)
2019-07-25 13:37:06 +00:00
var setts RequestFilteringSettings
// HELPERS
// SAFE BROWSING
// SAFE SEARCH
// PARENTAL
// FILTERING
// BENCHMARKS
// HELPERS
func purgeCaches() {
if gctx.safebrowsingCache != nil {
gctx.safebrowsingCache.Clear()
2018-08-30 14:25:33 +00:00
}
if gctx.parentalCache != nil {
gctx.parentalCache.Clear()
}
if gctx.safeSearchCache != nil {
gctx.safeSearchCache.Clear()
2018-08-30 14:25:33 +00:00
}
}
func _Func() string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return path.Base(f.Name())
}
func NewForTest(c *Config, filters map[int]string) *Dnsfilter {
2019-07-25 13:37:06 +00:00
setts = RequestFilteringSettings{}
setts.FilteringEnabled = true
2019-07-23 17:01:50 +00:00
if c != nil {
c.SafeBrowsingCacheSize = 1000
c.SafeSearchCacheSize = 1000
c.ParentalCacheSize = 1000
c.CacheTime = 30
2019-07-25 13:37:06 +00:00
setts.SafeSearchEnabled = c.SafeSearchEnabled
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
setts.ParentalEnabled = c.ParentalEnabled
2019-07-23 17:01:50 +00:00
}
d := New(c, filters)
purgeCaches()
return d
2018-08-30 14:25:33 +00:00
}
func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
t.Helper()
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
2018-08-30 14:25:33 +00:00
if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err)
}
if !ret.IsFiltered {
t.Errorf("Expected hostname %s to match", hostname)
}
}
2019-05-22 15:30:27 +00:00
func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname string, ip string, qtype uint16) {
t.Helper()
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(hostname, qtype, &setts)
if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err)
}
if !ret.IsFiltered {
t.Errorf("Expected hostname %s to match", hostname)
}
2019-01-24 17:11:01 +00:00
if ret.IP == nil || ret.IP.String() != ip {
t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP)
}
}
2018-08-30 14:25:33 +00:00
func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
t.Helper()
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
2018-08-30 14:25:33 +00:00
if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err)
}
if ret.IsFiltered {
t.Errorf("Expected hostname %s to not match", hostname)
}
}
func TestEtcHostsMatching(t *testing.T) {
addr := "216.239.38.120"
2019-05-22 15:30:27 +00:00
addr6 := "::1"
text := fmt.Sprintf(" %s google.com www.google.com # enforce google's safesearch \n%s google.com\n0.0.0.0 block.com\n",
addr, addr6)
filters := make(map[int]string)
filters[0] = text
d := NewForTest(nil, filters)
defer d.Close()
2019-05-22 15:30:27 +00:00
d.checkMatchIP(t, "google.com", addr, dns.TypeA)
d.checkMatchIP(t, "www.google.com", addr, dns.TypeA)
d.checkMatchEmpty(t, "subdomain.google.com")
d.checkMatchEmpty(t, "example.org")
2019-05-22 15:30:27 +00:00
// IPv6 address
d.checkMatchIP(t, "google.com", addr6, dns.TypeAAAA)
// block both IPv4 and IPv6
d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA)
d.checkMatchIP(t, "block.com", "::", dns.TypeAAAA)
}
// SAFE BROWSING
2018-08-30 14:25:33 +00:00
func TestSafeBrowsing(t *testing.T) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close()
gctx.stats.Safebrowsing.Requests = 0
d.checkMatch(t, "wmconvirus.narod.ru")
d.checkMatch(t, "test.wmconvirus.narod.ru")
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, "pornhub.com")
// test cached result
d.safeBrowsingServer = "127.0.0.1"
d.checkMatch(t, "wmconvirus.narod.ru")
d.checkMatchEmpty(t, "pornhub.com")
d.safeBrowsingServer = defaultSafebrowsingServer
2018-08-30 14:25:33 +00:00
}
func TestParallelSB(t *testing.T) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close()
2018-08-30 14:25:33 +00:00
t.Run("group", func(t *testing.T) {
for i := 0; i < 100; i++ {
t.Run(fmt.Sprintf("aaa%d", i), func(t *testing.T) {
t.Parallel()
d.checkMatch(t, "wmconvirus.narod.ru")
d.checkMatch(t, "test.wmconvirus.narod.ru")
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, "pornhub.com")
})
}
})
}
// SAFE SEARCH
func TestSafeSearch(t *testing.T) {
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
val, ok := d.SafeSearchDomain("www.google.com")
if !ok {
t.Errorf("Expected safesearch to find result for www.google.com")
}
if val != "forcesafesearch.google.com" {
t.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com")
}
}
2019-02-22 13:34:36 +00:00
func TestCheckHostSafeSearchYandex(t *testing.T) {
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2019-02-22 13:34:36 +00:00
// Slice of yandex domains
yandex := []string{"yAndeX.ru", "YANdex.COM", "yandex.ua", "yandex.by", "yandex.kz", "www.yandex.com"}
// Check host for each domain
for _, host := range yandex {
2019-07-25 13:37:06 +00:00
result, err := d.CheckHost(host, dns.TypeA, &setts)
2019-02-22 13:34:36 +00:00
if err != nil {
t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err)
}
if result.IP.String() != "213.180.193.56" {
t.Errorf("SafeSearch doesn't work for yandex domain `%s`", host)
}
}
}
func TestCheckHostSafeSearchGoogle(t *testing.T) {
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2019-02-22 13:34:36 +00:00
// Slice of google domains
googleDomains := []string{"www.google.com", "www.google.im", "www.google.co.in", "www.google.iq", "www.google.is", "www.google.it", "www.google.je"}
// Check host for each domain
for _, host := range googleDomains {
2019-07-25 13:37:06 +00:00
result, err := d.CheckHost(host, dns.TypeA, &setts)
2019-02-22 13:34:36 +00:00
if err != nil {
t.Errorf("SafeSearch doesn't work for %s cause %s", host, err)
}
if result.IP == nil {
t.Errorf("SafeSearch doesn't work for %s", host)
}
}
}
2019-02-25 15:56:51 +00:00
func TestSafeSearchCacheYandex(t *testing.T) {
d := NewForTest(nil, nil)
defer d.Close()
2019-02-22 13:34:36 +00:00
domain := "yandex.ru"
2019-02-25 15:56:51 +00:00
var result Result
var err error
2019-02-22 13:34:36 +00:00
// Check host with disabled safesearch
2019-07-25 13:37:06 +00:00
result, err = d.CheckHost(domain, dns.TypeA, &setts)
2019-02-25 15:56:51 +00:00
if err != nil {
t.Fatalf("Cannot check host due to %s", err)
}
2019-02-22 13:34:36 +00:00
if result.IP != nil {
t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain)
}
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2019-07-25 13:37:06 +00:00
result, err = d.CheckHost(domain, dns.TypeA, &setts)
2019-02-22 13:34:36 +00:00
if err != nil {
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
}
// Fir yandex we already know valid ip
if result.IP.String() != "213.180.193.56" {
t.Fatalf("Wrong IP for %s safesearch: %s", domain, result.IP.String())
}
// Check cache
2019-07-23 17:01:50 +00:00
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
2019-02-22 13:34:36 +00:00
if !isFound {
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
}
if cachedValue.IP.String() != "213.180.193.56" {
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
}
}
2019-02-25 15:56:51 +00:00
func TestSafeSearchCacheGoogle(t *testing.T) {
d := NewForTest(nil, nil)
defer d.Close()
2019-02-22 13:34:36 +00:00
domain := "www.google.ru"
2019-07-25 13:37:06 +00:00
result, err := d.CheckHost(domain, dns.TypeA, &setts)
2019-02-25 15:56:51 +00:00
if err != nil {
t.Fatalf("Cannot check host due to %s", err)
}
2019-02-22 13:34:36 +00:00
if result.IP != nil {
t.Fatalf("SafeSearch is not enabled but there is an answer!")
}
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2019-02-22 13:34:36 +00:00
// Let's lookup for safesearch domain
safeDomain, ok := d.SafeSearchDomain(domain)
if !ok {
t.Fatalf("Failed to get safesearch domain for %s", domain)
}
ips, err := net.LookupIP(safeDomain)
if err != nil {
t.Fatalf("Failed to lookup for %s", safeDomain)
}
2019-05-30 12:36:39 +00:00
t.Logf("IP addresses: %v", ips)
2019-02-25 11:58:54 +00:00
ip := ips[0]
2019-02-22 13:34:36 +00:00
for _, i := range ips {
2019-05-30 12:36:39 +00:00
if i.To4() != nil {
2019-02-22 13:34:36 +00:00
ip = i
2019-05-30 12:36:39 +00:00
break
2019-02-22 13:34:36 +00:00
}
}
2019-07-25 13:37:06 +00:00
result, err = d.CheckHost(domain, dns.TypeA, &setts)
2019-02-22 13:34:36 +00:00
if err != nil {
t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err)
}
if result.IP.String() != ip.String() {
2019-05-30 12:36:39 +00:00
t.Fatalf("Wrong IP for %s safesearch: %s. Should be: %s",
domain, result.IP.String(), ip)
2019-02-22 13:34:36 +00:00
}
// Check cache
2019-07-23 17:01:50 +00:00
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain)
2019-02-22 13:34:36 +00:00
if !isFound {
t.Fatalf("Safesearch cache doesn't work for %s!", domain)
}
if cachedValue.IP.String() != ip.String() {
t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String())
}
}
// PARENTAL
2018-08-30 14:25:33 +00:00
func TestParentalControl(t *testing.T) {
d := NewForTest(&Config{ParentalEnabled: true}, nil)
defer d.Close()
2018-12-06 13:54:48 +00:00
d.ParentalSensitivity = 3
2018-08-30 14:25:33 +00:00
d.checkMatch(t, "pornhub.com")
d.checkMatch(t, "www.pornhub.com")
d.checkMatchEmpty(t, "www.yandex.ru")
d.checkMatchEmpty(t, "yandex.ru")
d.checkMatchEmpty(t, "api.jquery.com")
// test cached result
d.parentalServer = "127.0.0.1"
d.checkMatch(t, "pornhub.com")
d.checkMatchEmpty(t, "yandex.ru")
d.parentalServer = defaultParentalServer
2018-08-30 14:25:33 +00:00
}
// FILTERING
2018-08-30 14:25:33 +00:00
var blockingRules = "||example.org^\n"
var whitelistRules = "||example.org^\n@@||test.example.org\n"
var importantRules = "@@||example.org^\n||test.example.org^$important\n"
var regexRules = "/example\\.org/\n@@||test.example.org^\n"
var maskRules = "test*.example.org^\nexam*.com\n"
2018-08-30 14:25:33 +00:00
var tests = []struct {
testname string
rules string
hostname string
isFiltered bool
reason Reason
2018-08-30 14:25:33 +00:00
}{
{"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList},
{"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound},
{"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound},
{"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound},
{"blocking", blockingRules, "example.org", true, FilteredBlackList},
{"blocking", blockingRules, "test.example.org", true, FilteredBlackList},
{"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList},
{"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound},
{"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound},
{"whitelist", whitelistRules, "example.org", true, FilteredBlackList},
{"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList},
{"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList},
{"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound},
{"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound},
{"important", importantRules, "example.org", false, NotFilteredWhiteList},
{"important", importantRules, "test.example.org", true, FilteredBlackList},
{"important", importantRules, "test.test.example.org", true, FilteredBlackList},
{"important", importantRules, "testexample.org", false, NotFilteredNotFound},
{"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound},
{"regex", regexRules, "example.org", true, FilteredBlackList},
{"regex", regexRules, "test.example.org", false, NotFilteredWhiteList},
{"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList},
{"regex", regexRules, "testexample.org", true, FilteredBlackList},
{"regex", regexRules, "onemoreexample.org", true, FilteredBlackList},
{"mask", maskRules, "test.example.org", true, FilteredBlackList},
{"mask", maskRules, "test2.example.org", true, FilteredBlackList},
{"mask", maskRules, "example.com", true, FilteredBlackList},
{"mask", maskRules, "exampleeee.com", true, FilteredBlackList},
{"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList},
{"mask", maskRules, "example.org", false, NotFilteredNotFound},
{"mask", maskRules, "testexample.org", false, NotFilteredNotFound},
{"mask", maskRules, "example.co.uk", false, NotFilteredNotFound},
2018-08-30 14:25:33 +00:00
}
func TestMatching(t *testing.T) {
for _, test := range tests {
t.Run(fmt.Sprintf("%s-%s", test.testname, test.hostname), func(t *testing.T) {
filters := make(map[int]string)
filters[0] = test.rules
d := NewForTest(nil, filters)
defer d.Close()
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(test.hostname, dns.TypeA, &setts)
2018-08-30 14:25:33 +00:00
if err != nil {
t.Errorf("Error while matching host %s: %s", test.hostname, err)
}
if ret.IsFiltered != test.isFiltered {
t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered)
}
if ret.Reason != test.reason {
t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String())
2018-08-30 14:25:33 +00:00
}
})
}
}
// CLIENT SETTINGS
2019-07-25 13:37:06 +00:00
func applyClientSettings(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,
// then apply per-client settings and check behaviour once again
func TestClientSettings(t *testing.T) {
var r Result
filters := make(map[int]string)
filters[0] = "||example.org^\n"
d := NewForTest(&Config{ParentalEnabled: true, SafeBrowsingEnabled: false}, filters)
defer d.Close()
d.ParentalSensitivity = 3
// no client settings:
// blocked by filters
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
if !r.IsFiltered || r.Reason != FilteredBlackList {
t.Fatalf("CheckHost FilteredBlackList")
}
// blocked by parental
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("pornhub.com", dns.TypeA, &setts)
if !r.IsFiltered || r.Reason != FilteredParental {
t.Fatalf("CheckHost FilteredParental")
}
// safesearch is disabled
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("wmconvirus.narod.ru", dns.TypeA, &setts)
if r.IsFiltered {
t.Fatalf("CheckHost safesearch")
}
// not blocked
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts)
assert.True(t, !r.IsFiltered)
// override client settings:
2019-07-25 13:37:06 +00:00
applyClientSettings(&setts)
// override filtering settings
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("example.org", dns.TypeA, &setts)
if r.IsFiltered {
t.Fatalf("CheckHost")
}
// override parental settings (force disable parental)
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("pornhub.com", dns.TypeA, &setts)
if r.IsFiltered {
t.Fatalf("CheckHost")
}
// override safesearch settings (force enable safesearch)
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("wmconvirus.narod.ru", dns.TypeA, &setts)
if !r.IsFiltered || r.Reason != FilteredSafeBrowsing {
t.Fatalf("CheckHost FilteredSafeBrowsing")
}
// blocked by additional rules
2019-07-25 13:37:06 +00:00
r, _ = d.CheckHost("facebook.com", dns.TypeA, &setts)
assert.True(t, r.IsFiltered && r.Reason == FilteredBlockedService)
}
// BENCHMARKS
2018-08-30 14:25:33 +00:00
func BenchmarkSafeBrowsing(b *testing.B) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close()
2018-08-30 14:25:33 +00:00
for n := 0; n < b.N; n++ {
hostname := "wmconvirus.narod.ru"
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
2018-08-30 14:25:33 +00:00
if err != nil {
b.Errorf("Error while matching host %s: %s", hostname, err)
}
if !ret.IsFiltered {
b.Errorf("Expected hostname %s to match", hostname)
}
}
}
func BenchmarkSafeBrowsingParallel(b *testing.B) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close()
2018-08-30 14:25:33 +00:00
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
hostname := "wmconvirus.narod.ru"
2019-07-25 13:37:06 +00:00
ret, err := d.CheckHost(hostname, dns.TypeA, &setts)
2018-08-30 14:25:33 +00:00
if err != nil {
b.Errorf("Error while matching host %s: %s", hostname, err)
}
if !ret.IsFiltered {
b.Errorf("Expected hostname %s to match", hostname)
}
}
})
}
func BenchmarkSafeSearch(b *testing.B) {
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2018-08-30 14:25:33 +00:00
for n := 0; n < b.N; n++ {
val, ok := d.SafeSearchDomain("www.google.com")
if !ok {
b.Errorf("Expected safesearch to find result for www.google.com")
}
if val != "forcesafesearch.google.com" {
b.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com")
}
}
}
func BenchmarkSafeSearchParallel(b *testing.B) {
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
defer d.Close()
2018-08-30 14:25:33 +00:00
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
val, ok := d.SafeSearchDomain("www.google.com")
if !ok {
b.Errorf("Expected safesearch to find result for www.google.com")
}
if val != "forcesafesearch.google.com" {
b.Errorf("Expected safesearch for google.com to be forcesafesearch.google.com")
}
}
})
}