mirror of
https://github.com/gentoo-mirror/guru.git
synced 2026-07-10 15:45:15 -04:00
Supports DoH, DoT and prometheus+grafana integration with very versatile cli and configuration options Signed-off-by: Rahil Bhimjiani <me@rahil.rocks>
2273 lines
71 KiB
Diff
2273 lines
71 KiB
Diff
diff --git a/cache/stringcache/chained_grouped_cache_test.go b/cache/stringcache/chained_grouped_cache_test.go
|
|
deleted file mode 100644
|
|
index e83f956..0000000
|
|
--- a/cache/stringcache/chained_grouped_cache_test.go
|
|
+++ /dev/null
|
|
@@ -1,93 +0,0 @@
|
|
-package stringcache_test
|
|
-
|
|
-import (
|
|
- "github.com/0xERR0R/blocky/cache/stringcache"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
-)
|
|
-
|
|
-var _ = Describe("Chained grouped cache", func() {
|
|
- Describe("Empty cache", func() {
|
|
- When("empty cache was created", func() {
|
|
- cache := stringcache.NewChainedGroupedCache()
|
|
-
|
|
- It("should have element count of 0", func() {
|
|
- Expect(cache.ElementCount("someGroup")).Should(BeNumerically("==", 0))
|
|
- })
|
|
-
|
|
- It("should not find any string", func() {
|
|
- Expect(cache.Contains("searchString", []string{"someGroup"})).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- })
|
|
- Describe("Delegation", func() {
|
|
- When("Chained cache contains delegates", func() {
|
|
- inMemoryCache1 := stringcache.NewInMemoryGroupedStringCache()
|
|
- inMemoryCache2 := stringcache.NewInMemoryGroupedStringCache()
|
|
- cache := stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2)
|
|
-
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("string1")
|
|
- factory.AddEntry("string2")
|
|
-
|
|
- It("cache should still have 0 element, since finish was not executed", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
|
|
- })
|
|
-
|
|
- It("factory has 4 elements (both caches)", func() {
|
|
- Expect(factory.Count()).Should(BeNumerically("==", 4))
|
|
- })
|
|
-
|
|
- It("should have element count of 4", func() {
|
|
- factory.Finish()
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4))
|
|
- })
|
|
-
|
|
- It("should find strings", func() {
|
|
- Expect(cache.Contains("string1", []string{"group1"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("string2", []string{"group1", "someOtherGroup"})).Should(ConsistOf("group1"))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Cache refresh", func() {
|
|
- When("cache with 2 groups was created", func() {
|
|
- inMemoryCache1 := stringcache.NewInMemoryGroupedStringCache()
|
|
- inMemoryCache2 := stringcache.NewInMemoryGroupedStringCache()
|
|
- cache := stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2)
|
|
-
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("g1")
|
|
- factory.AddEntry("both")
|
|
- factory.Finish()
|
|
-
|
|
- factory = cache.Refresh("group2")
|
|
- factory.AddEntry("g2")
|
|
- factory.AddEntry("both")
|
|
- factory.Finish()
|
|
-
|
|
- It("should contain 4 elements in 2 groups", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4))
|
|
- Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4))
|
|
- Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group1", "group2"))
|
|
- })
|
|
-
|
|
- It("Should replace group content on refresh", func() {
|
|
- factory := cache.Refresh("group1")
|
|
- factory.AddEntry("newString")
|
|
- factory.Finish()
|
|
-
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
|
|
- Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4))
|
|
- Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(BeEmpty())
|
|
- Expect(cache.Contains("newString", []string{"group1", "group2"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- })
|
|
- })
|
|
- })
|
|
-})
|
|
diff --git a/cache/stringcache/in_memory_grouped_cache_test.go b/cache/stringcache/in_memory_grouped_cache_test.go
|
|
deleted file mode 100644
|
|
index 7692935..0000000
|
|
--- a/cache/stringcache/in_memory_grouped_cache_test.go
|
|
+++ /dev/null
|
|
@@ -1,130 +0,0 @@
|
|
-package stringcache_test
|
|
-
|
|
-import (
|
|
- "github.com/0xERR0R/blocky/cache/stringcache"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
-)
|
|
-
|
|
-var _ = Describe("In-Memory grouped cache", func() {
|
|
- Describe("Empty cache", func() {
|
|
- When("empty cache was created", func() {
|
|
- cache := stringcache.NewInMemoryGroupedStringCache()
|
|
-
|
|
- It("should have element count of 0", func() {
|
|
- Expect(cache.ElementCount("someGroup")).Should(BeNumerically("==", 0))
|
|
- })
|
|
-
|
|
- It("should not find any string", func() {
|
|
- Expect(cache.Contains("searchString", []string{"someGroup"})).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- When("cache with one empty group", func() {
|
|
- cache := stringcache.NewInMemoryGroupedStringCache()
|
|
- factory := cache.Refresh("group1")
|
|
- factory.Finish()
|
|
-
|
|
- It("should have element count of 0", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
|
|
- })
|
|
-
|
|
- It("should not find any string", func() {
|
|
- Expect(cache.Contains("searchString", []string{"group1"})).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- })
|
|
- Describe("Cache creation", func() {
|
|
- When("cache with 1 group was created", func() {
|
|
- cache := stringcache.NewInMemoryGroupedStringCache()
|
|
-
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("string1")
|
|
- factory.AddEntry("string2")
|
|
-
|
|
- It("cache should still have 0 element, since finish was not executed", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
|
|
- })
|
|
-
|
|
- It("factory has 2 elements", func() {
|
|
- Expect(factory.Count()).Should(BeNumerically("==", 2))
|
|
- })
|
|
-
|
|
- It("should have element count of 2", func() {
|
|
- factory.Finish()
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
|
|
- })
|
|
-
|
|
- It("should find strings", func() {
|
|
- Expect(cache.Contains("string1", []string{"group1"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("string2", []string{"group1", "someOtherGroup"})).Should(ConsistOf("group1"))
|
|
- })
|
|
- })
|
|
- When("String grouped cache is used", func() {
|
|
- cache := stringcache.NewInMemoryGroupedStringCache()
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("string1")
|
|
- factory.AddEntry("/string2/")
|
|
- factory.Finish()
|
|
-
|
|
- It("should ignore regex", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
|
|
- Expect(cache.Contains("string1", []string{"group1"})).Should(ConsistOf("group1"))
|
|
- })
|
|
- })
|
|
- When("Regex grouped cache is used", func() {
|
|
- cache := stringcache.NewInMemoryGroupedRegexCache()
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("string1")
|
|
- factory.AddEntry("/string2/")
|
|
- factory.Finish()
|
|
-
|
|
- It("should ignore non-regex", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
|
|
- Expect(cache.Contains("string1", []string{"group1"})).Should(BeEmpty())
|
|
- Expect(cache.Contains("string2", []string{"group1"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("shouldalsomatchstring2", []string{"group1"})).Should(ConsistOf("group1"))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Cache refresh", func() {
|
|
- When("cache with 2 groups was created", func() {
|
|
- cache := stringcache.NewInMemoryGroupedStringCache()
|
|
-
|
|
- factory := cache.Refresh("group1")
|
|
-
|
|
- factory.AddEntry("g1")
|
|
- factory.AddEntry("both")
|
|
- factory.Finish()
|
|
-
|
|
- factory = cache.Refresh("group2")
|
|
- factory.AddEntry("g2")
|
|
- factory.AddEntry("both")
|
|
- factory.Finish()
|
|
-
|
|
- It("should contain 4 elements in 2 groups", func() {
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
|
|
- Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 2))
|
|
- Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group1", "group2"))
|
|
- })
|
|
-
|
|
- It("Should replace group content on refresh", func() {
|
|
- factory := cache.Refresh("group1")
|
|
- factory.AddEntry("newString")
|
|
- factory.Finish()
|
|
-
|
|
- Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
|
|
- Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 2))
|
|
- Expect(cache.Contains("g1", []string{"group1", "group2"})).Should(BeEmpty())
|
|
- Expect(cache.Contains("newString", []string{"group1", "group2"})).Should(ConsistOf("group1"))
|
|
- Expect(cache.Contains("g2", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- Expect(cache.Contains("both", []string{"group1", "group2"})).Should(ConsistOf("group2"))
|
|
- })
|
|
- })
|
|
- })
|
|
-})
|
|
diff --git a/lists/downloader_test.go b/lists/downloader_test.go
|
|
deleted file mode 100644
|
|
index 5387c86..0000000
|
|
--- a/lists/downloader_test.go
|
|
+++ /dev/null
|
|
@@ -1,218 +0,0 @@
|
|
-package lists
|
|
-
|
|
-import (
|
|
- "errors"
|
|
- "io"
|
|
- "net"
|
|
- "net/http"
|
|
- "net/http/httptest"
|
|
- "strings"
|
|
- "sync/atomic"
|
|
- "time"
|
|
-
|
|
- "github.com/0xERR0R/blocky/config"
|
|
- . "github.com/0xERR0R/blocky/evt"
|
|
- . "github.com/0xERR0R/blocky/helpertest"
|
|
- "github.com/0xERR0R/blocky/log"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
- "github.com/sirupsen/logrus/hooks/test"
|
|
-)
|
|
-
|
|
-var _ = Describe("Downloader", func() {
|
|
- var (
|
|
- sutConfig config.DownloaderConfig
|
|
- sut *httpDownloader
|
|
- failedDownloadCountEvtChannel chan string
|
|
- loggerHook *test.Hook
|
|
- )
|
|
- BeforeEach(func() {
|
|
- var err error
|
|
-
|
|
- sutConfig, err = config.WithDefaults[config.DownloaderConfig]()
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- failedDownloadCountEvtChannel = make(chan string, 5)
|
|
- // collect received events in the channel
|
|
- fn := func(url string) {
|
|
- failedDownloadCountEvtChannel <- url
|
|
- }
|
|
- Expect(Bus().Subscribe(CachingFailedDownloadChanged, fn)).Should(Succeed())
|
|
- DeferCleanup(func() {
|
|
- Expect(Bus().Unsubscribe(CachingFailedDownloadChanged, fn))
|
|
- })
|
|
-
|
|
- loggerHook = test.NewGlobal()
|
|
- log.Log().AddHook(loggerHook)
|
|
- DeferCleanup(loggerHook.Reset)
|
|
- })
|
|
-
|
|
- JustBeforeEach(func() {
|
|
- sut = newDownloader(sutConfig, nil)
|
|
- })
|
|
-
|
|
- Describe("NewDownloader", func() {
|
|
- It("Should use provided parameters", func() {
|
|
- transport := &http.Transport{}
|
|
-
|
|
- sut = NewDownloader(
|
|
- config.DownloaderConfig{
|
|
- Attempts: 5,
|
|
- Cooldown: config.Duration(2 * time.Second),
|
|
- Timeout: config.Duration(5 * time.Second),
|
|
- },
|
|
- transport,
|
|
- ).(*httpDownloader)
|
|
-
|
|
- Expect(sut.cfg.Attempts).Should(BeNumerically("==", 5))
|
|
- Expect(sut.cfg.Timeout).Should(BeNumerically("==", 5*time.Second))
|
|
- Expect(sut.cfg.Cooldown).Should(BeNumerically("==", 2*time.Second))
|
|
- Expect(sut.client.Transport).Should(BeIdenticalTo(transport))
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Download of a file", func() {
|
|
- var server *httptest.Server
|
|
- When("Download was successful", func() {
|
|
- BeforeEach(func() {
|
|
- server = TestServer("line.one\nline.two")
|
|
- DeferCleanup(server.Close)
|
|
-
|
|
- sut = newDownloader(sutConfig, nil)
|
|
- })
|
|
- It("Should return all lines from the file", func() {
|
|
- reader, err := sut.DownloadFile(server.URL)
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(reader).Should(Not(BeNil()))
|
|
- DeferCleanup(reader.Close)
|
|
- buf := new(strings.Builder)
|
|
- _, err = io.Copy(buf, reader)
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(buf.String()).Should(Equal("line.one\nline.two"))
|
|
- })
|
|
- })
|
|
- When("Server returns NOT_FOUND (404)", func() {
|
|
- BeforeEach(func() {
|
|
- server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
- rw.WriteHeader(http.StatusNotFound)
|
|
- }))
|
|
- DeferCleanup(server.Close)
|
|
-
|
|
- sutConfig.Attempts = 3
|
|
- })
|
|
- It("Should return error", func() {
|
|
- reader, err := sut.DownloadFile(server.URL)
|
|
-
|
|
- Expect(err).Should(HaveOccurred())
|
|
- Expect(reader).Should(BeNil())
|
|
- Expect(err.Error()).Should(Equal("got status code 404"))
|
|
- Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
|
|
- Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
|
|
- })
|
|
- })
|
|
- When("Wrong URL is defined", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.Attempts = 1
|
|
- })
|
|
- It("Should return error", func() {
|
|
- _, err := sut.DownloadFile("somewrongurl")
|
|
-
|
|
- Expect(err).Should(HaveOccurred())
|
|
- Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Can't download file: "))
|
|
- // failed download event was emitted only once
|
|
- Expect(failedDownloadCountEvtChannel).Should(HaveLen(1))
|
|
- Expect(failedDownloadCountEvtChannel).Should(Receive(Equal("somewrongurl")))
|
|
- })
|
|
- })
|
|
-
|
|
- When("If timeout occurs on first request", func() {
|
|
- var attempt uint64 = 1
|
|
-
|
|
- BeforeEach(func() {
|
|
- sutConfig = config.DownloaderConfig{
|
|
- Timeout: config.Duration(20 * time.Millisecond),
|
|
- Attempts: 3,
|
|
- Cooldown: config.Duration(time.Millisecond),
|
|
- }
|
|
-
|
|
- // should produce a timeout on first attempt
|
|
- server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
- a := atomic.LoadUint64(&attempt)
|
|
- atomic.AddUint64(&attempt, 1)
|
|
- if a == 1 {
|
|
- time.Sleep(500 * time.Millisecond)
|
|
- } else {
|
|
- _, err := rw.Write([]byte("blocked1.com"))
|
|
- Expect(err).Should(Succeed())
|
|
- }
|
|
- }))
|
|
- DeferCleanup(server.Close)
|
|
- })
|
|
- It("Should perform a retry and return file content", func() {
|
|
- reader, err := sut.DownloadFile(server.URL)
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(reader).Should(Not(BeNil()))
|
|
- DeferCleanup(reader.Close)
|
|
-
|
|
- buf := new(strings.Builder)
|
|
- _, err = io.Copy(buf, reader)
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(buf.String()).Should(Equal("blocked1.com"))
|
|
-
|
|
- // failed download event was emitted only once
|
|
- Expect(failedDownloadCountEvtChannel).Should(HaveLen(1))
|
|
- Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
|
|
- Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Temporary network err / Timeout occurred: "))
|
|
- })
|
|
- })
|
|
- When("If timeout occurs on all request", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig = config.DownloaderConfig{
|
|
- Timeout: config.Duration(10 * time.Millisecond),
|
|
- Attempts: 3,
|
|
- Cooldown: config.Duration(time.Millisecond),
|
|
- }
|
|
-
|
|
- // should always produce a timeout
|
|
- server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
- time.Sleep(20 * time.Millisecond)
|
|
- }))
|
|
- DeferCleanup(server.Close)
|
|
- })
|
|
- It("Should perform a retry until max retry attempt count is reached and return TransientError", func() {
|
|
- reader, err := sut.DownloadFile(server.URL)
|
|
- Expect(err).Should(HaveOccurred())
|
|
- Expect(errors.As(err, new(*TransientError))).Should(BeTrue())
|
|
- Expect(err.Error()).Should(ContainSubstring("Timeout"))
|
|
- Expect(reader).Should(BeNil())
|
|
-
|
|
- // failed download event was emitted 3 times
|
|
- Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
|
|
- Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
|
|
- })
|
|
- })
|
|
- When("DNS resolution of passed URL fails", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig = config.DownloaderConfig{
|
|
- Timeout: config.Duration(500 * time.Millisecond),
|
|
- Attempts: 3,
|
|
- Cooldown: 200 * config.Duration(time.Millisecond),
|
|
- }
|
|
- })
|
|
- It("Should perform a retry until max retry attempt count is reached and return DNSError", func() {
|
|
- reader, err := sut.DownloadFile("http://some.domain.which.does.not.exist")
|
|
- Expect(err).Should(HaveOccurred())
|
|
-
|
|
- var dnsError *net.DNSError
|
|
- Expect(errors.As(err, &dnsError)).Should(BeTrue(), "received error %w", err)
|
|
- Expect(reader).Should(BeNil())
|
|
-
|
|
- // failed download event was emitted 3 times
|
|
- Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
|
|
- Expect(failedDownloadCountEvtChannel).Should(Receive(Equal("http://some.domain.which.does.not.exist")))
|
|
- Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Name resolution err: "))
|
|
- })
|
|
- })
|
|
- })
|
|
-})
|
|
diff --git a/lists/list_cache_benchmark_test.go b/lists/list_cache_benchmark_test.go
|
|
deleted file mode 100644
|
|
index fedf2fd..0000000
|
|
--- a/lists/list_cache_benchmark_test.go
|
|
+++ /dev/null
|
|
@@ -1,29 +0,0 @@
|
|
-package lists
|
|
-
|
|
-import (
|
|
- "testing"
|
|
-
|
|
- "github.com/0xERR0R/blocky/config"
|
|
-)
|
|
-
|
|
-func BenchmarkRefresh(b *testing.B) {
|
|
- file1, _ := createTestListFile(b.TempDir(), 100000)
|
|
- file2, _ := createTestListFile(b.TempDir(), 150000)
|
|
- file3, _ := createTestListFile(b.TempDir(), 130000)
|
|
- lists := map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(file1, file2, file3),
|
|
- }
|
|
-
|
|
- cfg := config.SourceLoadingConfig{
|
|
- Concurrency: 5,
|
|
- RefreshPeriod: config.Duration(-1),
|
|
- }
|
|
- downloader := NewDownloader(config.DownloaderConfig{}, nil)
|
|
- cache, _ := NewListCache(ListCacheTypeBlacklist, cfg, lists, downloader)
|
|
-
|
|
- b.ReportAllocs()
|
|
-
|
|
- for n := 0; n < b.N; n++ {
|
|
- cache.Refresh()
|
|
- }
|
|
-}
|
|
diff --git a/lists/list_cache_test.go b/lists/list_cache_test.go
|
|
deleted file mode 100644
|
|
index 2d08cfb..0000000
|
|
--- a/lists/list_cache_test.go
|
|
+++ /dev/null
|
|
@@ -1,496 +0,0 @@
|
|
-package lists
|
|
-
|
|
-import (
|
|
- "bufio"
|
|
- "context"
|
|
- "errors"
|
|
- "fmt"
|
|
- "io"
|
|
- "math/rand"
|
|
- "net/http/httptest"
|
|
- "os"
|
|
- "strings"
|
|
-
|
|
- "github.com/0xERR0R/blocky/config"
|
|
- . "github.com/0xERR0R/blocky/evt"
|
|
- "github.com/0xERR0R/blocky/lists/parsers"
|
|
- "github.com/0xERR0R/blocky/log"
|
|
- "github.com/0xERR0R/blocky/util"
|
|
- "github.com/sirupsen/logrus"
|
|
-
|
|
- . "github.com/0xERR0R/blocky/helpertest"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
-)
|
|
-
|
|
-var _ = Describe("ListCache", func() {
|
|
- var (
|
|
- tmpDir *TmpFolder
|
|
- emptyFile, file1, file2, file3 *TmpFile
|
|
- server1, server2, server3 *httptest.Server
|
|
-
|
|
- sut *ListCache
|
|
- sutConfig config.SourceLoadingConfig
|
|
-
|
|
- listCacheType ListCacheType
|
|
- lists map[string][]config.BytesSource
|
|
- downloader FileDownloader
|
|
- mockDownloader *MockDownloader
|
|
- )
|
|
-
|
|
- BeforeEach(func() {
|
|
- var err error
|
|
-
|
|
- listCacheType = ListCacheTypeBlacklist
|
|
-
|
|
- sutConfig, err = config.WithDefaults[config.SourceLoadingConfig]()
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- sutConfig.RefreshPeriod = -1
|
|
-
|
|
- downloader = NewDownloader(config.DownloaderConfig{}, nil)
|
|
- mockDownloader = nil
|
|
-
|
|
- server1 = TestServer("blocked1.com\nblocked1a.com\n192.168.178.55")
|
|
- DeferCleanup(server1.Close)
|
|
- server2 = TestServer("blocked2.com")
|
|
- DeferCleanup(server2.Close)
|
|
- server3 = TestServer("blocked3.com\nblocked1a.com")
|
|
- DeferCleanup(server3.Close)
|
|
-
|
|
- tmpDir = NewTmpFolder("ListCache")
|
|
- Expect(tmpDir.Error).Should(Succeed())
|
|
- DeferCleanup(tmpDir.Clean)
|
|
-
|
|
- emptyFile = tmpDir.CreateStringFile("empty", "#empty file")
|
|
- Expect(emptyFile.Error).Should(Succeed())
|
|
-
|
|
- emptyFile = tmpDir.CreateStringFile("empty", "#empty file")
|
|
- Expect(emptyFile.Error).Should(Succeed())
|
|
- file1 = tmpDir.CreateStringFile("file1", "blocked1.com", "blocked1a.com")
|
|
- Expect(file1.Error).Should(Succeed())
|
|
- file2 = tmpDir.CreateStringFile("file2", "blocked2.com")
|
|
- Expect(file2.Error).Should(Succeed())
|
|
- file3 = tmpDir.CreateStringFile("file3", "blocked3.com", "blocked1a.com")
|
|
- Expect(file3.Error).Should(Succeed())
|
|
- })
|
|
-
|
|
- JustBeforeEach(func() {
|
|
- var err error
|
|
-
|
|
- Expect(lists).ShouldNot(BeNil(), "bad test: forgot to set `lists`")
|
|
-
|
|
- if mockDownloader != nil {
|
|
- downloader = mockDownloader
|
|
- }
|
|
-
|
|
- sut, err = NewListCache(listCacheType, sutConfig, lists, downloader)
|
|
- Expect(err).Should(Succeed())
|
|
- })
|
|
-
|
|
- Describe("List cache and matching", func() {
|
|
- When("List is empty", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr0": config.NewBytesSources(emptyFile.Path),
|
|
- }
|
|
- })
|
|
-
|
|
- When("Query with empty", func() {
|
|
- It("should not panic", func() {
|
|
- group := sut.Match("", []string{"gr0"})
|
|
- Expect(group).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
-
|
|
- It("should not match anything", func() {
|
|
- group := sut.Match("google.com", []string{"gr1"})
|
|
- Expect(group).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- When("List becomes empty on refresh", func() {
|
|
- BeforeEach(func() {
|
|
- mockDownloader = newMockDownloader(func(res chan<- string, err chan<- error) {
|
|
- res <- "blocked1.com"
|
|
- res <- "# nothing"
|
|
- })
|
|
-
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {mockDownloader.ListSource()},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should delete existing elements from group cache", func(ctx context.Context) {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- err := sut.refresh(ctx)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- group = sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- When("List has invalid lines", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {
|
|
- config.TextBytesSource(
|
|
- "inlinedomain1.com",
|
|
- "invaliddomain!",
|
|
- "inlinedomain2.com",
|
|
- ),
|
|
- },
|
|
- }
|
|
- })
|
|
-
|
|
- It("should still other domains", func() {
|
|
- group := sut.Match("inlinedomain1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("inlinedomain2.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- When("a temporary/transient err occurs on download", func() {
|
|
- BeforeEach(func() {
|
|
- // should produce a transient error on second and third attempt
|
|
- mockDownloader = newMockDownloader(func(res chan<- string, err chan<- error) {
|
|
- res <- "blocked1.com\nblocked2.com\n"
|
|
- err <- &TransientError{inner: errors.New("boom")}
|
|
- err <- &TransientError{inner: errors.New("boom")}
|
|
- })
|
|
-
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {mockDownloader.ListSource()},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should not delete existing elements from group cache", func(ctx context.Context) {
|
|
- By("Lists loaded without timeout", func() {
|
|
- Eventually(func(g Gomega) {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- g.Expect(group).Should(ContainElement("gr1"))
|
|
- }, "1s").Should(Succeed())
|
|
- })
|
|
-
|
|
- Expect(sut.refresh(ctx)).Should(HaveOccurred())
|
|
-
|
|
- By("List couldn't be loaded due to timeout", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
-
|
|
- sut.Refresh()
|
|
-
|
|
- By("List couldn't be loaded due to timeout", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- })
|
|
- When("non transient err occurs on download", func() {
|
|
- BeforeEach(func() {
|
|
- // should produce a non transient error on second attempt
|
|
- mockDownloader = newMockDownloader(func(res chan<- string, err chan<- error) {
|
|
- res <- "blocked1.com"
|
|
- err <- errors.New("boom")
|
|
- })
|
|
-
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {mockDownloader.ListSource()},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should keep existing elements from group cache", func(ctx context.Context) {
|
|
- By("Lists loaded without err", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
-
|
|
- Expect(sut.refresh(ctx)).Should(HaveOccurred())
|
|
-
|
|
- By("Lists from first load is kept", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- })
|
|
- When("Configuration has 3 external working urls", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(server1.URL, server2.URL),
|
|
- "gr2": config.NewBytesSources(server3.URL),
|
|
- }
|
|
- })
|
|
-
|
|
- It("should download the list and match against", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr2"})
|
|
- Expect(group).Should(ContainElement("gr2"))
|
|
- })
|
|
- })
|
|
- When("Configuration has some faulty urls", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(server1.URL, server2.URL, "doesnotexist"),
|
|
- "gr2": config.NewBytesSources(server3.URL, "someotherfile"),
|
|
- }
|
|
- })
|
|
-
|
|
- It("should download the list and match against", func() {
|
|
- group := sut.Match("blocked1.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElements("gr1", "gr2"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr2"})
|
|
- Expect(group).Should(ContainElement("gr2"))
|
|
- })
|
|
- })
|
|
- When("List will be updated", func() {
|
|
- resultCnt := 0
|
|
-
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(server1.URL),
|
|
- }
|
|
-
|
|
- _ = Bus().SubscribeOnce(BlockingCacheGroupChanged, func(listType ListCacheType, group string, cnt int) {
|
|
- resultCnt = cnt
|
|
- })
|
|
- })
|
|
-
|
|
- It("event should be fired and contain count of elements in downloaded lists", func() {
|
|
- group := sut.Match("blocked1.com", []string{})
|
|
- Expect(group).Should(BeEmpty())
|
|
- Expect(resultCnt).Should(Equal(3))
|
|
- })
|
|
- })
|
|
- When("multiple groups are passed", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(file1.Path, file2.Path),
|
|
- "gr2": config.NewBytesSources("file://" + file3.Path),
|
|
- }
|
|
- })
|
|
-
|
|
- It("should match", func() {
|
|
- Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(3))
|
|
- Expect(sut.groupedCache.ElementCount("gr2")).Should(Equal(2))
|
|
-
|
|
- group := sut.Match("blocked1.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr1", "gr2"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("blocked1a.com", []string{"gr2"})
|
|
- Expect(group).Should(ContainElement("gr2"))
|
|
- })
|
|
- })
|
|
- When("group with bigger files", func() {
|
|
- It("should match", func() {
|
|
- file1, lines1 := createTestListFile(GinkgoT().TempDir(), 10000)
|
|
- file2, lines2 := createTestListFile(GinkgoT().TempDir(), 15000)
|
|
- file3, lines3 := createTestListFile(GinkgoT().TempDir(), 13000)
|
|
- lists := map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(file1, file2, file3),
|
|
- }
|
|
-
|
|
- sut, err := NewListCache(ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(lines1 + lines2 + lines3))
|
|
- })
|
|
- })
|
|
- When("inline list content is defined", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {config.TextBytesSource(
|
|
- "inlinedomain1.com",
|
|
- "#some comment",
|
|
- "inlinedomain2.com",
|
|
- )},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should match", func() {
|
|
- Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(2))
|
|
- group := sut.Match("inlinedomain1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("inlinedomain2.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- When("Text file can't be parsed", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {
|
|
- config.TextBytesSource(
|
|
- "inlinedomain1.com",
|
|
- "lineTooLong"+strings.Repeat("x", bufio.MaxScanTokenSize), // too long
|
|
- ),
|
|
- },
|
|
- }
|
|
- })
|
|
-
|
|
- It("should still match already imported strings", func() {
|
|
- group := sut.Match("inlinedomain1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- When("Text file has too many errors", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.MaxErrorsPerSource = 0
|
|
- sutConfig.Strategy = config.StartStrategyTypeFailOnError
|
|
- })
|
|
- It("should fail parsing", func() {
|
|
- lists := map[string][]config.BytesSource{
|
|
- "gr1": {
|
|
- config.TextBytesSource("invaliddomain!"), // too many errors since `maxErrorsPerSource` is 0
|
|
- },
|
|
- }
|
|
-
|
|
- _, err := NewListCache(ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
|
- Expect(err).ShouldNot(Succeed())
|
|
- Expect(err).Should(MatchError(parsers.ErrTooManyErrors))
|
|
- })
|
|
- })
|
|
- When("file has end of line comment", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {config.TextBytesSource("inlinedomain1.com#a comment")},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should still parse the domain", func() {
|
|
- group := sut.Match("inlinedomain1.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- When("inline regex content is defined", func() {
|
|
- BeforeEach(func() {
|
|
- lists = map[string][]config.BytesSource{
|
|
- "gr1": {config.TextBytesSource("/^apple\\.(de|com)$/")},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should match", func() {
|
|
- group := sut.Match("apple.com", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
-
|
|
- group = sut.Match("apple.de", []string{"gr1"})
|
|
- Expect(group).Should(ContainElement("gr1"))
|
|
- })
|
|
- })
|
|
- })
|
|
- Describe("LogConfig", func() {
|
|
- var (
|
|
- logger *logrus.Entry
|
|
- hook *log.MockLoggerHook
|
|
- )
|
|
-
|
|
- BeforeEach(func() {
|
|
- logger, hook = log.NewMockEntry()
|
|
- })
|
|
-
|
|
- It("should print list configuration", func() {
|
|
- lists := map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources(server1.URL, server2.URL),
|
|
- "gr2": {config.TextBytesSource("inline", "definition")},
|
|
- }
|
|
-
|
|
- sut, err := NewListCache(ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- sut.LogConfig(logger)
|
|
- Expect(hook.Calls).ShouldNot(BeEmpty())
|
|
- Expect(hook.Messages).Should(ContainElement(ContainSubstring("gr1:")))
|
|
- Expect(hook.Messages).Should(ContainElement(ContainSubstring("gr2:")))
|
|
- Expect(hook.Messages).Should(ContainElement(ContainSubstring("TOTAL:")))
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("StartStrategy", func() {
|
|
- When("async load is enabled", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.Strategy = config.StartStrategyTypeFast
|
|
- })
|
|
-
|
|
- It("should never return an error", func() {
|
|
- lists := map[string][]config.BytesSource{
|
|
- "gr1": config.NewBytesSources("doesnotexist"),
|
|
- }
|
|
-
|
|
- _, err := NewListCache(ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
|
- Expect(err).Should(Succeed())
|
|
- })
|
|
- })
|
|
- })
|
|
-})
|
|
-
|
|
-type MockDownloader struct {
|
|
- util.MockCallSequence[string]
|
|
-}
|
|
-
|
|
-func newMockDownloader(driver func(res chan<- string, err chan<- error)) *MockDownloader {
|
|
- return &MockDownloader{util.NewMockCallSequence(driver)}
|
|
-}
|
|
-
|
|
-func (m *MockDownloader) DownloadFile(_ string) (io.ReadCloser, error) {
|
|
- str, err := m.Call()
|
|
- if err != nil {
|
|
- return nil, err
|
|
- }
|
|
-
|
|
- return io.NopCloser(strings.NewReader(str)), nil
|
|
-}
|
|
-
|
|
-func (m *MockDownloader) ListSource() config.BytesSource {
|
|
- return config.BytesSource{
|
|
- Type: config.BytesSourceTypeHttp,
|
|
- From: "http://mock-downloader",
|
|
- }
|
|
-}
|
|
-
|
|
-func createTestListFile(dir string, totalLines int) (string, int) {
|
|
- file, err := os.CreateTemp(dir, "blocky")
|
|
- if err != nil {
|
|
- log.Log().Fatal(err)
|
|
- }
|
|
-
|
|
- w := bufio.NewWriter(file)
|
|
- for i := 0; i < totalLines; i++ {
|
|
- fmt.Fprintln(w, RandStringBytes(8+rand.Intn(10))+".com")
|
|
- }
|
|
- w.Flush()
|
|
-
|
|
- return file.Name(), totalLines
|
|
-}
|
|
-
|
|
-const (
|
|
- initCharpool = "abcdefghijklmnopqrstuvwxyz"
|
|
- contCharpool = initCharpool + "0123456789-"
|
|
-)
|
|
-
|
|
-func RandStringBytes(n int) string {
|
|
- b := make([]byte, n)
|
|
-
|
|
- pool := initCharpool
|
|
-
|
|
- for i := range b {
|
|
- b[i] = pool[rand.Intn(len(pool))]
|
|
-
|
|
- pool = contCharpool
|
|
- }
|
|
-
|
|
- return string(b)
|
|
-}
|
|
diff --git a/server/server_test.go b/server/server_test.go
|
|
deleted file mode 100644
|
|
index 65b30bc..0000000
|
|
--- a/server/server_test.go
|
|
+++ /dev/null
|
|
@@ -1,888 +0,0 @@
|
|
-package server
|
|
-
|
|
-import (
|
|
- "bytes"
|
|
- "encoding/base64"
|
|
- "encoding/json"
|
|
- "io"
|
|
- "net"
|
|
- "net/http"
|
|
- "strings"
|
|
- "sync/atomic"
|
|
- "time"
|
|
-
|
|
- "github.com/0xERR0R/blocky/api"
|
|
- "github.com/0xERR0R/blocky/config"
|
|
- . "github.com/0xERR0R/blocky/helpertest"
|
|
- . "github.com/0xERR0R/blocky/log"
|
|
- "github.com/0xERR0R/blocky/model"
|
|
- "github.com/0xERR0R/blocky/resolver"
|
|
- "github.com/0xERR0R/blocky/util"
|
|
- "github.com/creasty/defaults"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
-
|
|
- "github.com/miekg/dns"
|
|
-)
|
|
-
|
|
-var (
|
|
- mockClientName atomic.Value
|
|
- sut *Server
|
|
- err error
|
|
-)
|
|
-
|
|
-var _ = BeforeSuite(func() {
|
|
- var upstreamGoogle, upstreamFritzbox, upstreamClient config.Upstream
|
|
- googleMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
|
- if request.Question[0].Name == "error." {
|
|
- return nil
|
|
- }
|
|
- response, err := util.NewMsgWithAnswer(
|
|
- util.ExtractDomain(request.Question[0]), 123, A, "123.124.122.122",
|
|
- )
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- return response
|
|
- })
|
|
- DeferCleanup(googleMockUpstream.Close)
|
|
-
|
|
- fritzboxMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
|
- response, err := util.NewMsgWithAnswer(
|
|
- util.ExtractDomain(request.Question[0]), 3600, A, "192.168.178.2",
|
|
- )
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- return response
|
|
- })
|
|
- DeferCleanup(fritzboxMockUpstream.Close)
|
|
-
|
|
- clientMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
|
- var clientName string
|
|
- client := mockClientName.Load()
|
|
-
|
|
- if client != nil {
|
|
- clientName = mockClientName.Load().(string)
|
|
- }
|
|
-
|
|
- response, err := util.NewMsgWithAnswer(
|
|
- util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), clientName,
|
|
- )
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- return response
|
|
- })
|
|
- DeferCleanup(clientMockUpstream.Close)
|
|
-
|
|
- upstreamClient = clientMockUpstream.Start()
|
|
- upstreamFritzbox = fritzboxMockUpstream.Start()
|
|
- upstreamGoogle = googleMockUpstream.Start()
|
|
-
|
|
- tmpDir := NewTmpFolder("server")
|
|
- Expect(tmpDir.Error).Should(Succeed())
|
|
- DeferCleanup(tmpDir.Clean)
|
|
-
|
|
- certPem := writeCertPem(tmpDir)
|
|
- Expect(certPem.Error).Should(Succeed())
|
|
-
|
|
- keyPem := writeKeyPem(tmpDir)
|
|
- Expect(keyPem.Error).Should(Succeed())
|
|
-
|
|
- doubleclickFile := tmpDir.CreateStringFile("doubleclick.net.txt", "doubleclick.net", "doubleclick.net.cn")
|
|
- Expect(doubleclickFile.Error).Should(Succeed())
|
|
-
|
|
- bildFile := tmpDir.CreateStringFile("www.bild.de.txt", "www.bild.de")
|
|
- Expect(bildFile.Error).Should(Succeed())
|
|
-
|
|
- heiseFile := tmpDir.CreateStringFile("heise.de.txt", "heise.de")
|
|
- Expect(heiseFile.Error).Should(Succeed())
|
|
-
|
|
- youtubeFile := tmpDir.CreateStringFile("youtube.com.txt", "youtube.com")
|
|
- Expect(youtubeFile.Error).Should(Succeed())
|
|
-
|
|
- // create server
|
|
- sut, err = NewServer(&config.Config{
|
|
- CustomDNS: config.CustomDNSConfig{
|
|
- CustomTTL: config.Duration(3600 * time.Second),
|
|
- Mapping: config.CustomDNSMapping{
|
|
- HostIPs: map[string][]net.IP{
|
|
- "custom.lan": {net.ParseIP("192.168.178.55")},
|
|
- "lan.home": {net.ParseIP("192.168.178.56")},
|
|
- },
|
|
- },
|
|
- },
|
|
- Conditional: config.ConditionalUpstreamConfig{
|
|
- Mapping: config.ConditionalUpstreamMapping{
|
|
- Upstreams: map[string][]config.Upstream{
|
|
- "net.cn": {upstreamClient},
|
|
- "fritz.box": {upstreamFritzbox},
|
|
- },
|
|
- },
|
|
- },
|
|
- Blocking: config.BlockingConfig{
|
|
- BlackLists: map[string][]config.BytesSource{
|
|
- "ads": config.NewBytesSources(
|
|
- doubleclickFile.Path,
|
|
- bildFile.Path,
|
|
- heiseFile.Path,
|
|
- ),
|
|
- "youtube": config.NewBytesSources(youtubeFile.Path),
|
|
- },
|
|
- WhiteLists: map[string][]config.BytesSource{
|
|
- "ads": config.NewBytesSources(heiseFile.Path),
|
|
- "whitelist": config.NewBytesSources(heiseFile.Path),
|
|
- },
|
|
- ClientGroupsBlock: map[string][]string{
|
|
- "default": {"ads"},
|
|
- "clWhitelistOnly": {"whitelist"},
|
|
- "clAdsAndYoutube": {"ads", "youtube"},
|
|
- "clYoutubeOnly": {"youtube"},
|
|
- },
|
|
- BlockType: "zeroIp",
|
|
- BlockTTL: config.Duration(6 * time.Hour),
|
|
- },
|
|
- Upstreams: config.UpstreamsConfig{
|
|
- Groups: map[string][]config.Upstream{"default": {upstreamGoogle}},
|
|
- },
|
|
- ClientLookup: config.ClientLookupConfig{
|
|
- Upstream: upstreamClient,
|
|
- },
|
|
-
|
|
- Ports: config.PortsConfig{
|
|
- DNS: config.ListenConfig{"55555"},
|
|
- TLS: config.ListenConfig{"8853"},
|
|
- HTTP: config.ListenConfig{"4000"},
|
|
- HTTPS: config.ListenConfig{"4443"},
|
|
- },
|
|
- CertFile: certPem.Path,
|
|
- KeyFile: keyPem.Path,
|
|
- Prometheus: config.MetricsConfig{
|
|
- Enable: true,
|
|
- Path: "/metrics",
|
|
- },
|
|
- })
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- errChan := make(chan error, 10)
|
|
-
|
|
- // start server
|
|
- go func() {
|
|
- sut.Start(errChan)
|
|
- }()
|
|
- DeferCleanup(sut.Stop)
|
|
-
|
|
- Consistently(errChan, "1s").ShouldNot(Receive())
|
|
-})
|
|
-
|
|
-var _ = Describe("Running DNS server", func() {
|
|
- Describe("performing DNS request with running server", func() {
|
|
- BeforeEach(func() {
|
|
- mockClientName.Store("")
|
|
- // reset client cache
|
|
- res := sut.queryResolver
|
|
- for res != nil {
|
|
- if t, ok := res.(*resolver.ClientNamesResolver); ok {
|
|
- t.FlushCache()
|
|
-
|
|
- break
|
|
- }
|
|
- if c, ok := res.(resolver.ChainedResolver); ok {
|
|
- res = c.GetNext()
|
|
- } else {
|
|
- break
|
|
- }
|
|
- }
|
|
- })
|
|
-
|
|
- Context("DNS query is resolvable via external DNS", func() {
|
|
- It("should return valid answer", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("google.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("google.de.", A, "123.124.122.122"),
|
|
- HaveTTL(BeNumerically("==", 123)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Custom DNS entry with exact match", func() {
|
|
- It("should return valid answer", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("custom.lan.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("custom.lan.", A, "192.168.178.55"),
|
|
- HaveTTL(BeNumerically("==", 3600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Custom DNS entry with sub domain", func() {
|
|
- It("should return valid answer", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("host.lan.home.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("host.lan.home.", A, "192.168.178.56"),
|
|
- HaveTTL(BeNumerically("==", 3600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Conditional upstream", func() {
|
|
- It("should resolve query via conditional upstream resolver", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("host.fritz.box.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("host.fritz.box.", A, "192.168.178.2"),
|
|
- HaveTTL(BeNumerically("==", 3600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Conditional upstream blocking", func() {
|
|
- It("Query should be blocked, domain is in default group", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("doubleclick.net.cn.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("doubleclick.net.cn.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Blocking default group", func() {
|
|
- It("Query should be blocked, domain is in default group", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("doubleclick.net.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("doubleclick.net.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("Blocking default group with sub domain", func() {
|
|
- It("Query with subdomain should be blocked, domain is in default group", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("www.bild.de.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("no blocking default group with sub domain", func() {
|
|
- It("Query with should not be blocked, sub domain is not in blacklist", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("bild.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("bild.de.", A, "123.124.122.122"),
|
|
- HaveTTL(BeNumerically("<=", 123)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("domain is on white and blacklist default group", func() {
|
|
- It("Query with should not be blocked, domain is on white and blacklist", func() {
|
|
- Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("heise.de.", A, "123.124.122.122"),
|
|
- HaveTTL(BeNumerically("<=", 123)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("domain is on client specific white list", func() {
|
|
- It("Query with should not be blocked, domain is on client's white list", func() {
|
|
- mockClientName.Store("clWhitelistOnly")
|
|
- Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("heise.de.", A, "123.124.122.122"),
|
|
- HaveTTL(BeNumerically("<=", 123)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("block client whitelist only", func() {
|
|
- It("Query with should be blocked, client has only whitelist, domain is not on client's white list", func() {
|
|
- mockClientName.Store("clWhitelistOnly")
|
|
- Expect(requestServer(util.NewMsgWithQuestion("google.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("google.de.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("block client with 2 groups", func() {
|
|
- It("Query with should be blocked, domain is on black list", func() {
|
|
- mockClientName.Store("clAdsAndYoutube")
|
|
-
|
|
- Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("www.bild.de.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
-
|
|
- Expect(requestServer(util.NewMsgWithQuestion("youtube.com.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("youtube.com.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("client with 1 group: no block if domain in other group", func() {
|
|
- It("Query with should not be blocked, domain is on black list in another group", func() {
|
|
- mockClientName.Store("clYoutubeOnly")
|
|
-
|
|
- Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("www.bild.de.", A, "123.124.122.122"),
|
|
- HaveTTL(BeNumerically("<=", 123)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("block client with 1 group", func() {
|
|
- It("Query with should not blocked, domain is on black list in client's group", func() {
|
|
- mockClientName.Store("clYoutubeOnly")
|
|
-
|
|
- Expect(requestServer(util.NewMsgWithQuestion("youtube.com.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- BeDNSRecord("youtube.com.", A, "0.0.0.0"),
|
|
- HaveTTL(BeNumerically("==", 21600)),
|
|
- ))
|
|
- })
|
|
- })
|
|
- Context("health check", func() {
|
|
- It("Should always return dummy response", func() {
|
|
- resp := requestServer(util.NewMsgWithQuestion("healthcheck.blocky.", A))
|
|
-
|
|
- Expect(resp.Answer).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Prometheus endpoint", func() {
|
|
- When("Prometheus URL is called", func() {
|
|
- It("should return prometheus data", func() {
|
|
- resp, err := http.Get("http://localhost:4000/metrics")
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
|
- })
|
|
- })
|
|
- })
|
|
- Describe("Root endpoint", func() {
|
|
- When("Root URL is called", func() {
|
|
- It("should return root page", func() {
|
|
- resp, err := http.Get("http://localhost:4000/")
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).Should(
|
|
- SatisfyAll(
|
|
- HaveHTTPStatus(http.StatusOK),
|
|
- HaveHTTPHeaderWithValue("Content-type", "text/html; charset=UTF-8"),
|
|
- ))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Query Rest API", func() {
|
|
- When("Query API is called", func() {
|
|
- It("Should process the query", func() {
|
|
- req := api.QueryRequest{
|
|
- Query: "google.de",
|
|
- Type: "A",
|
|
- }
|
|
- jsonValue, err := json.Marshal(req)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/api/query", "application/json", bytes.NewBuffer(jsonValue))
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
- defer resp.Body.Close()
|
|
-
|
|
- Expect(resp).Should(
|
|
- SatisfyAll(
|
|
- HaveHTTPStatus(http.StatusOK),
|
|
- HaveHTTPHeaderWithValue("Content-type", "application/json"),
|
|
- ))
|
|
-
|
|
- var result api.QueryResult
|
|
- err = json.NewDecoder(resp.Body).Decode(&result)
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(result.Response).Should(Equal("A (123.124.122.122)"))
|
|
- })
|
|
- })
|
|
- When("Wrong request type is used", func() {
|
|
- It("Should return internal error", func() {
|
|
- req := api.QueryRequest{
|
|
- Query: "google.de",
|
|
- Type: "WrongType",
|
|
- }
|
|
- jsonValue, err := json.Marshal(req)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/api/query", "application/json", bytes.NewBuffer(jsonValue))
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
|
|
- })
|
|
- })
|
|
- When("Internal error occurs", func() {
|
|
- It("Should return internal error", func() {
|
|
- req := api.QueryRequest{
|
|
- Query: "error.",
|
|
- Type: "A",
|
|
- }
|
|
- jsonValue, err := json.Marshal(req)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/api/query", "application/json", bytes.NewBuffer(jsonValue))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
- Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
|
|
- })
|
|
- })
|
|
- When("Request is malformed", func() {
|
|
- It("Should return internal error", func() {
|
|
- jsonValue := []byte("")
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/api/query", "application/json", bytes.NewBuffer(jsonValue))
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("DOH endpoint", func() {
|
|
- Context("DOH over GET (RFC 8484)", func() {
|
|
- When("DOH get request with 'example.com' is performed", func() {
|
|
- It("should get a valid response", func() {
|
|
- resp, err := http.Get("http://localhost:4000/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB")
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
|
- Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/dns-message"))
|
|
-
|
|
- rawMsg, err := io.ReadAll(resp.Body)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- msg := new(dns.Msg)
|
|
- err = msg.Unpack(rawMsg)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
|
|
- })
|
|
- })
|
|
- When("Request does not contain a valid DNS message", func() {
|
|
- It("should return 'Bad Request'", func() {
|
|
- resp, err := http.Get("http://localhost:4000/dns-query?dns=xxxx")
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
|
|
- })
|
|
- })
|
|
- When("Request's parameter does not contain a valid base64'", func() {
|
|
- It("should return 'Bad Request'", func() {
|
|
- resp, err := http.Get("http://localhost:4000/dns-query?dns=äöä")
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
|
|
- })
|
|
- })
|
|
- When("Request does not contain a dns parameter", func() {
|
|
- It("should return 'Bad Request'", func() {
|
|
- resp, err := http.Get("http://localhost:4000/dns-query?test")
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
|
|
- })
|
|
- })
|
|
- When("Request's dns parameter is too long'", func() {
|
|
- It("should return 'URI Too Long'", func() {
|
|
- longBase64msg := base64.StdEncoding.EncodeToString([]byte(strings.Repeat("t", 513)))
|
|
-
|
|
- resp, err := http.Get("http://localhost:4000/dns-query?dns=" + longBase64msg)
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusRequestURITooLong))
|
|
- })
|
|
- })
|
|
- })
|
|
- Context("DOH over POST (RFC 8484)", func() {
|
|
- When("DOH post request with 'example.com' is performed", func() {
|
|
- It("should get a valid response", func() {
|
|
- msg := util.NewMsgWithQuestion("www.example.com.", A)
|
|
- rawDNSMessage, err := msg.Pack()
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/dns-query",
|
|
- "application/dns-message", bytes.NewReader(rawDNSMessage))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(
|
|
- SatisfyAll(
|
|
- HaveHTTPStatus(http.StatusOK),
|
|
- HaveHTTPHeaderWithValue("Content-type", "application/dns-message"),
|
|
- ))
|
|
-
|
|
- rawMsg, err := io.ReadAll(resp.Body)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- msg = new(dns.Msg)
|
|
- err = msg.Unpack(rawMsg)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
|
|
- })
|
|
- It("should get a valid response, clientId is passed", func() {
|
|
- msg := util.NewMsgWithQuestion("www.example.com.", A)
|
|
- rawDNSMessage, err := msg.Pack()
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/dns-query/client123",
|
|
- "application/dns-message", bytes.NewReader(rawDNSMessage))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(
|
|
- SatisfyAll(
|
|
- HaveHTTPStatus(http.StatusOK),
|
|
- HaveHTTPHeaderWithValue("Content-type", "application/dns-message"),
|
|
- ))
|
|
- rawMsg, err := io.ReadAll(resp.Body)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- msg = new(dns.Msg)
|
|
- err = msg.Unpack(rawMsg)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
|
|
- })
|
|
- })
|
|
- When("POST payload exceeds 512 bytes", func() {
|
|
- It("should return 'Payload Too Large'", func() {
|
|
- largeMessage := []byte(strings.Repeat("t", 513))
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/dns-query", "application/dns-message", bytes.NewReader(largeMessage))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusRequestEntityTooLarge))
|
|
- })
|
|
- })
|
|
- When("Request has wrong type", func() {
|
|
- It("should return 'Unsupported Media Type'", func() {
|
|
- resp, err := http.Post("http://localhost:4000/dns-query", "application/text", bytes.NewReader([]byte("a")))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusUnsupportedMediaType))
|
|
- })
|
|
- })
|
|
- When("Internal error occurs", func() {
|
|
- It("should return 'Internal server error'", func() {
|
|
- msg := util.NewMsgWithQuestion("error.", A)
|
|
- rawDNSMessage, err := msg.Pack()
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- resp, err := http.Post("http://localhost:4000/dns-query",
|
|
- "application/dns-message", bytes.NewReader(rawDNSMessage))
|
|
- Expect(err).Should(Succeed())
|
|
- DeferCleanup(resp.Body.Close)
|
|
-
|
|
- Expect(resp).Should(HaveHTTPStatus(http.StatusInternalServerError))
|
|
- })
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Server create", func() {
|
|
- var (
|
|
- cfg config.Config
|
|
- cErr error
|
|
- )
|
|
- BeforeEach(func() {
|
|
- cErr = defaults.Set(&cfg)
|
|
-
|
|
- Expect(cErr).Should(Succeed())
|
|
-
|
|
- cfg.Upstreams.Groups = map[string][]config.Upstream{
|
|
- "default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}},
|
|
- }
|
|
-
|
|
- cfg.Redis.Address = "test-fail"
|
|
- })
|
|
- When("Server is created", func() {
|
|
- It("is created without redis connection", func() {
|
|
- _, err := NewServer(&cfg)
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
- })
|
|
- It("can't be created if redis server is unavailable", func() {
|
|
- cfg.Redis.Required = true
|
|
-
|
|
- _, err := NewServer(&cfg)
|
|
-
|
|
- Expect(err).ShouldNot(Succeed())
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Server start", Label("XX"), func() {
|
|
- When("Server start is called", func() {
|
|
- It("start was called 2 times, start should fail", func() {
|
|
- // create server
|
|
- server, err := NewServer(&config.Config{
|
|
- Upstreams: config.UpstreamsConfig{
|
|
- Groups: map[string][]config.Upstream{
|
|
- "default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}},
|
|
- },
|
|
- },
|
|
- CustomDNS: config.CustomDNSConfig{
|
|
- Mapping: config.CustomDNSMapping{
|
|
- HostIPs: map[string][]net.IP{
|
|
- "custom.lan": {net.ParseIP("192.168.178.55")},
|
|
- "lan.home": {net.ParseIP("192.168.178.56")},
|
|
- },
|
|
- },
|
|
- },
|
|
- Blocking: config.BlockingConfig{BlockType: "zeroIp"},
|
|
- Ports: config.PortsConfig{
|
|
- DNS: config.ListenConfig{":55556"},
|
|
- },
|
|
- })
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- errChan := make(chan error, 10)
|
|
-
|
|
- // start server
|
|
- go server.Start(errChan)
|
|
-
|
|
- DeferCleanup(server.Stop)
|
|
-
|
|
- Consistently(errChan, "1s").ShouldNot(Receive())
|
|
-
|
|
- // start again -> should fail
|
|
- server.Start(errChan)
|
|
-
|
|
- Eventually(errChan).Should(Receive())
|
|
- })
|
|
- })
|
|
- })
|
|
- Describe("Server stop", func() {
|
|
- When("Stop is called", func() {
|
|
- It("stop was called 2 times, start should fail", func() {
|
|
- // create server
|
|
- server, err := NewServer(&config.Config{
|
|
- Upstreams: config.UpstreamsConfig{
|
|
- Groups: map[string][]config.Upstream{
|
|
- "default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}},
|
|
- },
|
|
- },
|
|
- CustomDNS: config.CustomDNSConfig{
|
|
- Mapping: config.CustomDNSMapping{
|
|
- HostIPs: map[string][]net.IP{
|
|
- "custom.lan": {net.ParseIP("192.168.178.55")},
|
|
- "lan.home": {net.ParseIP("192.168.178.56")},
|
|
- },
|
|
- },
|
|
- },
|
|
- Blocking: config.BlockingConfig{BlockType: "zeroIp"},
|
|
- Ports: config.PortsConfig{
|
|
- DNS: config.ListenConfig{"127.0.0.1:55557"},
|
|
- },
|
|
- })
|
|
-
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- errChan := make(chan error, 10)
|
|
-
|
|
- // start server
|
|
- go func() {
|
|
- server.Start(errChan)
|
|
- }()
|
|
-
|
|
- time.Sleep(100 * time.Millisecond)
|
|
-
|
|
- err = server.Stop()
|
|
-
|
|
- // stop server, should be ok
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- // stop again, should raise error
|
|
- err = server.Stop()
|
|
-
|
|
- Expect(err).Should(HaveOccurred())
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("NewServer with strict upstream strategy", func() {
|
|
- It("successfully returns upstream branches", func() {
|
|
- branches, err := createUpstreamBranches(&config.Config{
|
|
- Upstreams: config.UpstreamsConfig{
|
|
- Strategy: config.UpstreamStrategyStrict,
|
|
- Groups: config.UpstreamGroups{
|
|
- "default": {{Host: "0.0.0.0"}},
|
|
- },
|
|
- },
|
|
- },
|
|
- nil)
|
|
-
|
|
- Expect(err).ToNot(HaveOccurred())
|
|
- Expect(branches).ToNot(BeNil())
|
|
- Expect(branches).To(HaveLen(1))
|
|
- _ = branches["default"].(*resolver.StrictResolver)
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("create query resolver", func() {
|
|
- When("some upstream returns error", func() {
|
|
- It("create query resolver should return error", func() {
|
|
- r, err := createQueryResolver(&config.Config{
|
|
- StartVerifyUpstream: true,
|
|
- Upstreams: config.UpstreamsConfig{
|
|
- Groups: config.UpstreamGroups{
|
|
- "default": {{Host: "0.0.0.0"}},
|
|
- },
|
|
- },
|
|
- },
|
|
- nil, nil)
|
|
-
|
|
- Expect(err).To(HaveOccurred())
|
|
- Expect(err).To(MatchError(ContainSubstring("creation of upstream branches failed: ")))
|
|
- Expect(r).To(BeNil())
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("resolve client IP", func() {
|
|
- Context("UDP address", func() {
|
|
- It("should correct resolve client IP", func() {
|
|
- ip, protocol := resolveClientIPAndProtocol(&net.UDPAddr{IP: net.ParseIP("192.168.178.88")})
|
|
- Expect(ip).Should(Equal(net.ParseIP("192.168.178.88")))
|
|
- Expect(protocol).Should(Equal(model.RequestProtocolUDP))
|
|
- })
|
|
- })
|
|
- Context("TCP address", func() {
|
|
- It("should correct resolve client IP", func() {
|
|
- ip, protocol := resolveClientIPAndProtocol(&net.TCPAddr{IP: net.ParseIP("192.168.178.88")})
|
|
- Expect(ip).Should(Equal(net.ParseIP("192.168.178.88")))
|
|
- Expect(protocol).Should(Equal(model.RequestProtocolTCP))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("self-signed certificate creation", func() {
|
|
- var (
|
|
- cfg config.Config
|
|
- cErr error
|
|
- )
|
|
- BeforeEach(func() {
|
|
- cErr = defaults.Set(&cfg)
|
|
-
|
|
- Expect(cErr).Should(Succeed())
|
|
-
|
|
- cfg.Upstreams.Groups = map[string][]config.Upstream{
|
|
- "default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}},
|
|
- }
|
|
- })
|
|
-
|
|
- It("should create self-signed certificate if key/cert files are not provided", func() {
|
|
- cfg.KeyFile = ""
|
|
- cfg.CertFile = ""
|
|
- cfg.Ports = config.PortsConfig{
|
|
- HTTPS: []string{":14443"},
|
|
- }
|
|
-
|
|
- sut, err := NewServer(&cfg)
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(sut.cert.Certificate).ShouldNot(BeNil())
|
|
- })
|
|
- })
|
|
-})
|
|
-
|
|
-func requestServer(request *dns.Msg) *dns.Msg {
|
|
- conn, err := net.Dial("udp", ":55555")
|
|
- if err != nil {
|
|
- Log().Fatal("could not connect to server: ", err)
|
|
- }
|
|
- defer conn.Close()
|
|
-
|
|
- msg, err := request.Pack()
|
|
- if err != nil {
|
|
- Log().Fatal("can't pack request: ", err)
|
|
- }
|
|
-
|
|
- _, err = conn.Write(msg)
|
|
- if err != nil {
|
|
- Log().Fatal("can't send request to server: ", err)
|
|
- }
|
|
-
|
|
- out := make([]byte, 1024)
|
|
-
|
|
- if _, err := conn.Read(out); err == nil {
|
|
- response := new(dns.Msg)
|
|
-
|
|
- err = response.Unpack(out)
|
|
-
|
|
- if err != nil {
|
|
- Log().Fatal("can't unpack response: ", err)
|
|
- }
|
|
-
|
|
- return response
|
|
- }
|
|
-
|
|
- Log().Fatal("could not read from connection", err)
|
|
-
|
|
- return nil
|
|
-}
|
|
-
|
|
-func writeCertPem(tmpDir *TmpFolder) *TmpFile {
|
|
- return tmpDir.CreateStringFile("cert.pem",
|
|
- "-----BEGIN CERTIFICATE-----",
|
|
- "MIICMzCCAZygAwIBAgIRAJCCrDTGEtZfRpxDY1KAoswwDQYJKoZIhvcNAQELBQAw",
|
|
- "EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2",
|
|
- "MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw",
|
|
- "gYkCgYEA4mEaF5yWYYrTfMgRXdBpgGnqsHIADQWlw7BIJWD/gNp+fgp4TUZ/7ggV",
|
|
- "rrvRORvRFjw14avd9L9EFP7XLi8ViU3uoE1UWI32MlrKqLbGNCXyUIApIoqlbRg6",
|
|
- "iErxIk5+ChzFuysQOx01S2yv/ML6dx7NOGHs1S38MUzRZtcXBH8CAwEAAaOBhjCB",
|
|
- "gzAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/",
|
|
- "BAUwAwEB/zAdBgNVHQ4EFgQUslNI6tYIv909RttHaZVMS/u/VYYwLAYDVR0RBCUw",
|
|
- "I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB",
|
|
- "CwUAA4GBAJ2gRpQHr5Qj7dt26bYVMdN4JGXTsvjbVrJfKI0VfPGJ+SUY/uTVBUeX",
|
|
- "+Cwv4DFEPBlNx/lzuUkwmRaExC4/w81LWwxe5KltYsjyJuYowiUbLZ6tzLaQ9Bcx",
|
|
- "jxClAVvgj90TGYOwsv6ESOX7GWteN1FlD3+jk7vefjFagaKKFYR9",
|
|
- "-----END CERTIFICATE-----")
|
|
-}
|
|
-
|
|
-func writeKeyPem(tmpDir *TmpFolder) *TmpFile {
|
|
- return tmpDir.CreateStringFile("key.pem",
|
|
- "-----BEGIN PRIVATE KEY-----",
|
|
- "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOJhGheclmGK03zI",
|
|
- "EV3QaYBp6rByAA0FpcOwSCVg/4Dafn4KeE1Gf+4IFa670Tkb0RY8NeGr3fS/RBT+",
|
|
- "1y4vFYlN7qBNVFiN9jJayqi2xjQl8lCAKSKKpW0YOohK8SJOfgocxbsrEDsdNUts",
|
|
- "r/zC+ncezThh7NUt/DFM0WbXFwR/AgMBAAECgYEA1exixstPhI+2+OTrHFc1S4dL",
|
|
- "oz+ncqbSlZEBLGl0KWTQQfVM5+FmRR7Yto1/0lLKDBQL6t0J2x3fjWOhHmCaHKZA",
|
|
- "VAvZ8+OKxwofih3hlO0tGCB8szUJygp2FAmd0rOUqvPQ+PTohZEUXyDaB8MOIbX+",
|
|
- "qoo7g19+VlbyKqmM8HkCQQDs4GQJwEn7GXKllSMyOfiYnjQM2pwsqO0GivXkH+p3",
|
|
- "+h5KDp4g3O4EbmbrvZyZB2euVsBjW3pFMu+xPXuOXf91AkEA9KfC7LGLD2OtLmrM",
|
|
- "iCZAqHlame+uEEDduDmqjTPnNKUWVeRtYKMF5Hltbeo1jMXMSbVZ+fRWKfQ+HAhQ",
|
|
- "xjFJowJAV6U7PqRoe0FSO1QwXrA2fHnk9nCY4qlqckZObyckAVqJhIteFPjKFNeo",
|
|
- "u0dAPxsPUOGGc/zwA9Sx/ZmrMuUy1QJBALl7bqawO/Ng6G0mfwZBqgeQaYYHVnnw",
|
|
- "E6iV353J2eHpvzNDSUFYlyEOhk4soIindSf0m9CK08Be8a+jBkocF+0CQQC+Hi7L",
|
|
- "kZV1slpW82BxYIhs9Gb0OQgK8SsI4aQPTFGUarQXXAm4eRqBO0kaG+jGX6TtW353",
|
|
- "EHK784GIxwVXKej/",
|
|
- "-----END PRIVATE KEY-----")
|
|
-}
|
|
diff --git a/resolver/hosts_file_resolver_test.go b/resolver/hosts_file_resolver_test.go
|
|
deleted file mode 100644
|
|
index eef4591..0000000
|
|
--- a/resolver/hosts_file_resolver_test.go
|
|
+++ /dev/null
|
|
@@ -1,376 +0,0 @@
|
|
-package resolver
|
|
-
|
|
-import (
|
|
- "context"
|
|
- "time"
|
|
-
|
|
- "github.com/0xERR0R/blocky/config"
|
|
- . "github.com/0xERR0R/blocky/helpertest"
|
|
- "github.com/0xERR0R/blocky/log"
|
|
- . "github.com/0xERR0R/blocky/model"
|
|
- "github.com/miekg/dns"
|
|
- . "github.com/onsi/ginkgo/v2"
|
|
- . "github.com/onsi/gomega"
|
|
- "github.com/stretchr/testify/mock"
|
|
-)
|
|
-
|
|
-var _ = Describe("HostsFileResolver", func() {
|
|
- var (
|
|
- TTL = uint32(time.Now().Second())
|
|
-
|
|
- sut *HostsFileResolver
|
|
- sutConfig config.HostsFileConfig
|
|
- m *mockResolver
|
|
- tmpDir *TmpFolder
|
|
- tmpFile *TmpFile
|
|
- )
|
|
-
|
|
- Describe("Type", func() {
|
|
- It("follows conventions", func() {
|
|
- expectValidResolverType(sut)
|
|
- })
|
|
- })
|
|
-
|
|
- BeforeEach(func() {
|
|
- tmpDir = NewTmpFolder("HostsFileResolver")
|
|
- Expect(tmpDir.Error).Should(Succeed())
|
|
- DeferCleanup(tmpDir.Clean)
|
|
-
|
|
- tmpFile = writeHostFile(tmpDir)
|
|
- Expect(tmpFile.Error).Should(Succeed())
|
|
-
|
|
- sutConfig = config.HostsFileConfig{
|
|
- Sources: config.NewBytesSources(tmpFile.Path),
|
|
- HostsTTL: config.Duration(time.Duration(TTL) * time.Second),
|
|
- FilterLoopback: true,
|
|
- Loading: config.SourceLoadingConfig{
|
|
- RefreshPeriod: -1,
|
|
- MaxErrorsPerSource: 5,
|
|
- },
|
|
- }
|
|
- })
|
|
-
|
|
- JustBeforeEach(func() {
|
|
- var err error
|
|
-
|
|
- sut, err = NewHostsFileResolver(sutConfig, systemResolverBootstrap)
|
|
- Expect(err).Should(Succeed())
|
|
-
|
|
- m = &mockResolver{}
|
|
- m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
|
|
- sut.Next(m)
|
|
- })
|
|
-
|
|
- Describe("IsEnabled", func() {
|
|
- It("is true", func() {
|
|
- Expect(sut.IsEnabled()).Should(BeTrue())
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("LogConfig", func() {
|
|
- It("should log something", func() {
|
|
- logger, hook := log.NewMockEntry()
|
|
-
|
|
- sut.LogConfig(logger)
|
|
-
|
|
- Expect(hook.Calls).ShouldNot(BeEmpty())
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Using hosts file", func() {
|
|
- When("Hosts file cannot be located", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig = config.HostsFileConfig{
|
|
- Sources: config.NewBytesSources("/this/file/does/not/exist"),
|
|
- HostsTTL: config.Duration(time.Duration(TTL) * time.Second),
|
|
- }
|
|
- })
|
|
- It("should not parse any hosts", func() {
|
|
- Expect(sut.cfg.Sources).ShouldNot(BeEmpty())
|
|
- Expect(sut.hosts.v4.hosts).Should(BeEmpty())
|
|
- Expect(sut.hosts.v6.hosts).Should(BeEmpty())
|
|
- Expect(sut.hosts.v4.aliases).Should(BeEmpty())
|
|
- Expect(sut.hosts.v6.aliases).Should(BeEmpty())
|
|
- Expect(sut.hosts.isEmpty()).Should(BeTrue())
|
|
- })
|
|
- It("should go to next resolver on query", func() {
|
|
- Expect(sut.Resolve(newRequest("example.com.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeRESOLVED),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- ))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("Hosts file is not set", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.Deprecated.Filepath = new(config.BytesSource)
|
|
- sutConfig.Sources = nil
|
|
-
|
|
- m = &mockResolver{}
|
|
- m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
|
|
- sut.Next(m)
|
|
- })
|
|
- It("should not return an error", func() {
|
|
- err := sut.loadSources(context.Background())
|
|
- Expect(err).Should(Succeed())
|
|
- })
|
|
- It("should go to next resolver on query", func() {
|
|
- Expect(sut.Resolve(newRequest("example.com.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeRESOLVED),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- ))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("Hosts file can be located", func() {
|
|
- It("should parse it successfully", func() {
|
|
- Expect(sut).ShouldNot(BeNil())
|
|
- Expect(sut.hosts.v4.hosts).Should(HaveLen(5))
|
|
- Expect(sut.hosts.v6.hosts).Should(HaveLen(2))
|
|
- Expect(sut.hosts.v4.aliases).Should(HaveLen(4))
|
|
- Expect(sut.hosts.v6.aliases).Should(HaveLen(2))
|
|
- })
|
|
-
|
|
- When("filterLoopback is false", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.FilterLoopback = false
|
|
- })
|
|
-
|
|
- It("should parse it successfully", func() {
|
|
- Expect(sut).ShouldNot(BeNil())
|
|
- Expect(sut.hosts.v4.hosts).Should(HaveLen(7))
|
|
- Expect(sut.hosts.v6.hosts).Should(HaveLen(3))
|
|
- Expect(sut.hosts.v4.aliases).Should(HaveLen(5))
|
|
- Expect(sut.hosts.v6.aliases).Should(HaveLen(2))
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- When("Hosts file has too many errors", func() {
|
|
- BeforeEach(func() {
|
|
- tmpFile = tmpDir.CreateStringFile("hosts-too-many-errors.txt",
|
|
- "invalidip localhost",
|
|
- "127.0.0.1 localhost", // ok
|
|
- "127.0.0.1 # no host",
|
|
- "127.0.0.1 invalidhost!",
|
|
- "a.b.c.d localhost",
|
|
- "127.0.0.x localhost",
|
|
- "256.0.0.1 localhost",
|
|
- )
|
|
- Expect(tmpFile.Error).Should(Succeed())
|
|
-
|
|
- sutConfig.Sources = config.NewBytesSources(tmpFile.Path)
|
|
- })
|
|
-
|
|
- It("should not be used", func() {
|
|
- Expect(sut).ShouldNot(BeNil())
|
|
- Expect(sut.cfg.Sources).ShouldNot(BeEmpty())
|
|
- Expect(sut.hosts.v4.hosts).Should(BeEmpty())
|
|
- Expect(sut.hosts.v6.hosts).Should(BeEmpty())
|
|
- Expect(sut.hosts.v4.aliases).Should(BeEmpty())
|
|
- Expect(sut.hosts.v6.aliases).Should(BeEmpty())
|
|
- })
|
|
- })
|
|
-
|
|
- When("IPv4 mapping is defined for a host", func() {
|
|
- It("defined ipv4 query should be resolved", func() {
|
|
- Expect(sut.Resolve(newRequest("ipv4host.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- BeDNSRecord("ipv4host.", A, "192.168.2.1"),
|
|
- HaveTTL(BeNumerically("==", TTL)),
|
|
- ))
|
|
- })
|
|
- It("defined ipv4 query for alias should be resolved", func() {
|
|
- Expect(sut.Resolve(newRequest("router2.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- BeDNSRecord("router2.", A, "10.0.0.1"),
|
|
- HaveTTL(BeNumerically("==", TTL)),
|
|
- ))
|
|
- })
|
|
- It("ipv4 query should return NOERROR and empty result", func() {
|
|
- Expect(sut.Resolve(newRequest("does.not.exist.", A))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveNoAnswer(),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- HaveResponseType(ResponseTypeRESOLVED),
|
|
- ))
|
|
- })
|
|
- })
|
|
-
|
|
- When("IPv6 mapping is defined for a host", func() {
|
|
- It("defined ipv6 query should be resolved", func() {
|
|
- Expect(sut.Resolve(newRequest("ipv6host.", AAAA))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- BeDNSRecord("ipv6host.", AAAA, "faaf:faaf:faaf:faaf::1"),
|
|
- HaveTTL(BeNumerically("==", TTL)),
|
|
- ))
|
|
- })
|
|
- It("ipv6 query should return NOERROR and empty result", func() {
|
|
- Expect(sut.Resolve(newRequest("does.not.exist.", AAAA))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveNoAnswer(),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- HaveResponseType(ResponseTypeRESOLVED),
|
|
- ))
|
|
- })
|
|
- })
|
|
-
|
|
- When("the domain is not known", func() {
|
|
- It("calls the next resolver", func() {
|
|
- resp, err := sut.Resolve(newRequest("not-in-hostsfile.tld.", A))
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("the question type is not handled", func() {
|
|
- It("calls the next resolver", func() {
|
|
- resp, err := sut.Resolve(newRequest("localhost.", MX))
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("Reverse DNS request is received", func() {
|
|
- It("should resolve the defined domain name", func() {
|
|
- By("ipv4 with one hostname", func() {
|
|
- Expect(sut.Resolve(newRequest("2.0.0.10.in-addr.arpa.", PTR))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- BeDNSRecord("2.0.0.10.in-addr.arpa.", PTR, "router3."),
|
|
- HaveTTL(BeNumerically("==", TTL)),
|
|
- ))
|
|
- })
|
|
- By("ipv4 with aliases", func() {
|
|
- Expect(sut.Resolve(newRequest("1.0.0.10.in-addr.arpa.", PTR))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- WithTransform(ToAnswer, ContainElements(
|
|
- BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router0."),
|
|
- BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router1."),
|
|
- BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router2."),
|
|
- )),
|
|
- ))
|
|
- })
|
|
- By("ipv6", func() {
|
|
- Expect(sut.Resolve(newRequest("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.", PTR))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- WithTransform(ToAnswer, ContainElements(
|
|
- BeDNSRecord("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.",
|
|
- PTR, "ipv6host."),
|
|
- BeDNSRecord("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.",
|
|
- PTR, "ipv6host.local.lan."),
|
|
- )),
|
|
- ))
|
|
- })
|
|
- })
|
|
-
|
|
- It("should ignore invalid PTR", func() {
|
|
- resp, err := sut.Resolve(newRequest("2.0.0.10.in-addr.fail.arpa.", PTR))
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
-
|
|
- When("filterLoopback is true", func() {
|
|
- It("calls the next resolver", func() {
|
|
- resp, err := sut.Resolve(newRequest("1.0.0.127.in-addr.arpa.", PTR))
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("the IP is not known", func() {
|
|
- It("calls the next resolver", func() {
|
|
- resp, err := sut.Resolve(newRequest("255.255.255.255.in-addr.arpa.", PTR))
|
|
- Expect(err).Should(Succeed())
|
|
- Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
-
|
|
- When("filterLoopback is false", func() {
|
|
- BeforeEach(func() {
|
|
- sutConfig.FilterLoopback = false
|
|
- })
|
|
-
|
|
- It("resolve the defined domain name", func() {
|
|
- Expect(sut.Resolve(newRequest("1.1.0.127.in-addr.arpa.", PTR))).
|
|
- Should(
|
|
- SatisfyAll(
|
|
- HaveResponseType(ResponseTypeHOSTSFILE),
|
|
- HaveReturnCode(dns.RcodeSuccess),
|
|
- WithTransform(ToAnswer, ContainElements(
|
|
- BeDNSRecord("1.1.0.127.in-addr.arpa.", PTR, "localhost2."),
|
|
- BeDNSRecord("1.1.0.127.in-addr.arpa.", PTR, "localhost2.local.lan."),
|
|
- )),
|
|
- ))
|
|
- })
|
|
- })
|
|
- })
|
|
- })
|
|
-
|
|
- Describe("Delegating to next resolver", func() {
|
|
- When("no hosts file is provided", func() {
|
|
- It("should delegate to next resolver", func() {
|
|
- _, err := sut.Resolve(newRequest("example.com.", A))
|
|
- Expect(err).Should(Succeed())
|
|
- // delegate was executed
|
|
- m.AssertExpectations(GinkgoT())
|
|
- })
|
|
- })
|
|
- })
|
|
-})
|
|
-
|
|
-func writeHostFile(tmpDir *TmpFolder) *TmpFile {
|
|
- return tmpDir.CreateStringFile("hosts.txt",
|
|
- "# Random comment",
|
|
- "127.0.0.1 localhost",
|
|
- "127.0.1.1 localhost2 localhost2.local.lan",
|
|
- "::1 localhost",
|
|
- "# Two empty lines to follow",
|
|
- "",
|
|
- "",
|
|
- "faaf:faaf:faaf:faaf::1 ipv6host ipv6host.local.lan",
|
|
- "192.168.2.1 ipv4host ipv4host.local.lan",
|
|
- "faaf:faaf:faaf:faaf::2 dualhost dualhost.local.lan",
|
|
- "192.168.2.2 dualhost dualhost.local.lan",
|
|
- "10.0.0.1 router0 router1 router2",
|
|
- "10.0.0.2 router3 # Another comment",
|
|
- "10.0.0.3 router4#comment without a space",
|
|
- "10.0.0.4 # Invalid entry",
|
|
- "300.300.300.300 invalid4 # Invalid IPv4",
|
|
- "abcd:efgh:ijkl::1 invalid6 # Invalid IPv6",
|
|
- "1.2.3.4 localhost", // localhost name but not localhost IP
|
|
-
|
|
- // from https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
|
- "fe80::1%lo0 localhost", // interface name
|
|
- )
|
|
-}
|