chore: test cleanup and refactoring (#509)

test: added additional assertions, introduced channels for bus event tests, refactoring, eliminating race conditions in tests, enable race check in tests
This commit is contained in:
Dimitri Herzog 2022-05-06 22:34:08 +02:00 committed by GitHub
parent 53814a2208
commit 41febafd41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 790 additions and 608 deletions

View File

@ -30,6 +30,9 @@ jobs:
- name: Test
run: make test
- name: Race detection
run: make race
- name: Upload results to codecov
run: bash <(curl -s https://codecov.io/bash) -t 48d6a1a8-a66e-4f27-9cc1-a7b91c4209b2

View File

@ -34,6 +34,9 @@ build: ## Build binary
test: ## run tests
go test -v -coverprofile=coverage.txt -covermode=atomic -cover ./...
race: ## run tests with race detector
go test -race -short ./...
lint: build ## run golangcli-lint checks
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0
$(shell go env GOPATH)/bin/golangci-lint run

View File

@ -117,18 +117,11 @@ func (e *ExpiringLRUCache) Put(key string, val interface{}, ttl time.Duration) {
expiresEpochMs := time.Now().UnixMilli() + ttl.Milliseconds()
el, found := e.lru.Get(key)
if found {
// update existing item
el.(*element).val = val
el.(*element).expiresEpochMs = expiresEpochMs
} else {
// add new item
e.lru.Add(key, &element{
val: val,
expiresEpochMs: expiresEpochMs,
})
}
// add new item
e.lru.Add(key, &element{
val: val,
expiresEpochMs: expiresEpochMs,
})
}
func (e *ExpiringLRUCache) Get(key string) (val interface{}, ttl time.Duration) {

View File

@ -8,8 +8,6 @@ import (
"github.com/0xERR0R/blocky/api"
"github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/util"
"github.com/spf13/cobra"
)
@ -24,7 +22,7 @@ func newBlockingCommand() *cobra.Command {
Args: cobra.NoArgs,
Aliases: []string{"on"},
Short: "Enable blocking",
Run: enableBlocking,
RunE: enableBlocking,
})
disableCommand := &cobra.Command{
@ -32,7 +30,7 @@ func newBlockingCommand() *cobra.Command {
Aliases: []string{"off"},
Args: cobra.NoArgs,
Short: "Disable blocking for certain duration",
Run: disableBlocking,
RunE: disableBlocking,
}
disableCommand.Flags().DurationP("duration", "d", 0, "duration in min")
disableCommand.Flags().StringArrayP("groups", "g", []string{}, "blocking groups to disable")
@ -42,64 +40,65 @@ func newBlockingCommand() *cobra.Command {
Use: "status",
Args: cobra.NoArgs,
Short: "Print the status of blocking resolver",
Run: statusBlocking,
RunE: statusBlocking,
})
return c
}
func enableBlocking(_ *cobra.Command, _ []string) {
func enableBlocking(_ *cobra.Command, _ []string) error {
resp, err := http.Get(apiURL(api.PathBlockingEnablePath))
if err != nil {
log.Log().Fatal("can't execute", err)
return
return fmt.Errorf("can't execute %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
log.Log().Info("OK")
} else {
log.Log().Fatal("NOK: ", resp.Status)
return fmt.Errorf("response NOK, Status: %s", resp.Status)
}
return nil
}
func disableBlocking(cmd *cobra.Command, _ []string) {
func disableBlocking(cmd *cobra.Command, _ []string) error {
duration, _ := cmd.Flags().GetDuration("duration")
groups, _ := cmd.Flags().GetStringArray("groups")
resp, err := http.Get(fmt.Sprintf("%s?duration=%s&groups=%s",
apiURL(api.PathBlockingDisablePath), duration, strings.Join(groups, ",")))
if err != nil {
util.FatalOnError("can't execute", err)
return
return fmt.Errorf("can't execute %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
log.Log().Info("OK")
} else {
util.FatalOnError("can't read response body", err)
log.Log().Fatalf("NOK: %s", resp.Status)
return fmt.Errorf("response NOK, Status: %s", resp.Status)
}
return nil
}
func statusBlocking(_ *cobra.Command, _ []string) {
func statusBlocking(_ *cobra.Command, _ []string) error {
resp, err := http.Get(apiURL(api.PathBlockingStatusPath))
if err != nil {
log.Log().Fatal("can't execute", err)
return
return fmt.Errorf("can't execute %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Log().Fatal("NOK: ", resp.Status)
return
return fmt.Errorf("response NOK, Status: %s", resp.Status)
}
var result api.BlockingStatus
err = json.NewDecoder(resp.Body).Decode(&result)
util.FatalOnError("can't read response: ", err)
if err != nil {
return fmt.Errorf("can't parse response %w", err)
}
if result.Enabled {
log.Log().Info("blocking enabled")
@ -111,4 +110,6 @@ func statusBlocking(_ *cobra.Command, _ []string) {
strings.Join(result.DisabledGroups, "; "), result.AutoEnableInSec)
}
}
return nil
}

View File

@ -7,7 +7,10 @@ import (
"net/url"
"strconv"
"github.com/sirupsen/logrus/hooks/test"
"github.com/0xERR0R/blocky/api"
"github.com/0xERR0R/blocky/log"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@ -15,8 +18,9 @@ import (
var _ = Describe("Blocking command", func() {
var (
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
loggerHook *test.Hook
)
JustBeforeEach(func() {
ts = testHTTPAPIServer(mockFn)
@ -26,20 +30,25 @@ var _ = Describe("Blocking command", func() {
})
BeforeEach(func() {
mockFn = func(w http.ResponseWriter, _ *http.Request) {}
loggerHook = test.NewGlobal()
log.Log().AddHook(loggerHook)
})
AfterEach(func() {
loggerHook.Reset()
})
Describe("enable blocking", func() {
When("Enable blocking is called via REST", func() {
It("should enable the blocking status", func() {
enableBlocking(newBlockingCommand(), []string{})
Expect(disableBlocking(newBlockingCommand(), []string{})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(Equal("OK"))
})
})
When("Wrong url is used", func() {
It("Should end with error", func() {
apiPort = 0
enableBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("connection refused"))
err := enableBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("connection refused"))
})
})
When("Server returns internal error", func() {
@ -49,25 +58,25 @@ var _ = Describe("Blocking command", func() {
}
})
It("Should end with error", func() {
enableBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(Equal("NOK: 500 Internal Server Error"))
err := enableBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("500 Internal Server Error"))
})
})
})
Describe("disable blocking", func() {
When("disable blocking is called via REST", func() {
It("should enable the blocking status", func() {
disableBlocking(newBlockingCommand(), []string{})
Expect(disableBlocking(newBlockingCommand(), []string{})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(Equal("OK"))
})
})
When("Wrong url is used", func() {
It("Should end with error", func() {
apiPort = 0
disableBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("connection refused"))
err := disableBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("connection refused"))
})
})
When("Server returns internal error", func() {
@ -77,9 +86,9 @@ var _ = Describe("Blocking command", func() {
}
})
It("Should end with error", func() {
disableBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(Equal("NOK: 500 Internal Server Error"))
err := disableBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("500 Internal Server Error"))
})
})
})
@ -98,7 +107,7 @@ var _ = Describe("Blocking command", func() {
}
})
It("should query the blocking status", func() {
statusBlocking(newBlockingCommand(), []string{})
Expect(statusBlocking(newBlockingCommand(), []string{})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(Equal("blocking enabled"))
})
})
@ -119,21 +128,21 @@ var _ = Describe("Blocking command", func() {
})
It("should show the blocking status with time", func() {
autoEnable = 5
statusBlocking(newBlockingCommand(), []string{})
Expect(statusBlocking(newBlockingCommand(), []string{})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(Equal("blocking disabled for groups: abc, for 5 seconds"))
})
It("should show the blocking status", func() {
autoEnable = 0
statusBlocking(newBlockingCommand(), []string{})
Expect(statusBlocking(newBlockingCommand(), []string{})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(Equal("blocking disabled for groups: abc"))
})
})
When("Wrong url is used", func() {
It("Should end with error", func() {
apiPort = 0
statusBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("connection refused"))
err := statusBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("connection refused"))
})
})
When("Server returns internal error", func() {
@ -143,9 +152,9 @@ var _ = Describe("Blocking command", func() {
}
})
It("Should end with error", func() {
statusBlocking(newBlockingCommand(), []string{})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(Equal("NOK: 500 Internal Server Error"))
err := statusBlocking(newBlockingCommand(), []string{})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("500 Internal Server Error"))
})
})
})

View File

@ -4,29 +4,13 @@ import (
"testing"
"github.com/0xERR0R/blocky/log"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus/hooks/test"
)
var (
fatal bool
loggerHook *test.Hook
)
func TestCmd(t *testing.T) {
log.Silence()
BeforeSuite(func() {
log.Log().ExitFunc = func(int) { fatal = true }
loggerHook = test.NewGlobal()
log.Log().AddHook(loggerHook)
})
AfterSuite(func() {
log.Log().ExitFunc = nil
loggerHook.Reset()
})
RegisterFailHandler(Fail)
RunSpecs(t, "Command Suite")
}

View File

@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"io/ioutil"
"net/http"
@ -26,25 +27,23 @@ func newRefreshCommand() *cobra.Command {
return &cobra.Command{
Use: "refresh",
Short: "refreshes all lists",
Run: refreshList,
RunE: refreshList,
}
}
func refreshList(_ *cobra.Command, _ []string) {
func refreshList(_ *cobra.Command, _ []string) error {
resp, err := http.Post(apiURL(api.PathListsRefresh), "application/json", nil)
if err != nil {
log.Log().Fatal("can't execute", err)
return
return fmt.Errorf("can't execute %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
log.Log().Fatalf("NOK: %s %s", resp.Status, string(body))
return
return fmt.Errorf("response NOK, %s %s", resp.Status, string(body))
}
log.Log().Info("OK")
return nil
}

View File

@ -4,14 +4,18 @@ import (
"net/http"
"net/http/httptest"
"github.com/0xERR0R/blocky/log"
"github.com/sirupsen/logrus/hooks/test"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Lists command", func() {
var (
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
loggerHook *test.Hook
)
JustBeforeEach(func() {
ts = testHTTPAPIServer(mockFn)
@ -21,6 +25,11 @@ var _ = Describe("Lists command", func() {
})
BeforeEach(func() {
mockFn = func(w http.ResponseWriter, _ *http.Request) {}
loggerHook = test.NewGlobal()
log.Log().AddHook(loggerHook)
})
AfterEach(func() {
loggerHook.Reset()
})
Describe("Call list refresh command", func() {
When("list refresh is executed", func() {
@ -42,9 +51,9 @@ var _ = Describe("Lists command", func() {
It("should end with error", func() {
c := newRefreshCommand()
c.SetArgs(make([]string, 0))
_ = c.Execute()
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("NOK: 500 Internal Server Error"))
err := c.Execute()
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("500 Internal Server Error"))
})
})
When("Url is wrong", func() {
@ -52,9 +61,9 @@ var _ = Describe("Lists command", func() {
apiPort = 0
c := newRefreshCommand()
c.SetArgs(make([]string, 0))
_ = c.Execute()
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("connection refused"))
err := c.Execute()
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("connection refused"))
})
})
})

View File

@ -3,13 +3,12 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/0xERR0R/blocky/api"
"github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/util"
"github.com/miekg/dns"
"github.com/spf13/cobra"
)
@ -20,7 +19,7 @@ func NewQueryCommand() *cobra.Command {
Use: "query <domain>",
Args: cobra.ExactArgs(1),
Short: "performs DNS query",
Run: query,
RunE: query,
}
c.Flags().StringP("type", "t", "A", "query type (A, AAAA, ...)")
@ -28,13 +27,12 @@ func NewQueryCommand() *cobra.Command {
return c
}
func query(cmd *cobra.Command, args []string) {
func query(cmd *cobra.Command, args []string) error {
typeFlag, _ := cmd.Flags().GetString("type")
qType := dns.StringToType[typeFlag]
if qType == dns.TypeNone {
log.Log().Fatalf("unknown query type '%s'", typeFlag)
return
return fmt.Errorf("unknown query type '%s'", typeFlag)
}
apiRequest := api.QueryRequest{
@ -42,32 +40,35 @@ func query(cmd *cobra.Command, args []string) {
Type: typeFlag,
}
jsonValue, err := json.Marshal(apiRequest)
util.FatalOnError("can't marshal request: ", err)
if err != nil {
return fmt.Errorf("can't marshal request: %w", err)
}
resp, err := http.Post(apiURL(api.PathQueryPath), "application/json", bytes.NewBuffer(jsonValue))
if err != nil {
log.Log().Fatal("can't execute", err)
return
return fmt.Errorf("can't execute: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
log.Log().Fatalf("NOK: %s %s", resp.Status, string(body))
return
return fmt.Errorf("response NOK, %s %s", resp.Status, string(body))
}
var result api.QueryResult
err = json.NewDecoder(resp.Body).Decode(&result)
util.FatalOnError("can't read response: ", err)
if err != nil {
return fmt.Errorf("can't read response: %w", err)
}
log.Log().Infof("Query result for '%s' (%s):", apiRequest.Query, apiRequest.Type)
log.Log().Infof("\treason: %20s", result.Reason)
log.Log().Infof("\tresponse type: %20s", result.ResponseType)
log.Log().Infof("\tresponse: %20s", result.Response)
log.Log().Infof("\treturn code: %20s", result.ReturnCode)
return nil
}

View File

@ -5,6 +5,9 @@ import (
"net/http"
"net/http/httptest"
"github.com/0xERR0R/blocky/log"
"github.com/sirupsen/logrus/hooks/test"
"github.com/0xERR0R/blocky/api"
. "github.com/onsi/ginkgo/v2"
@ -13,8 +16,9 @@ import (
var _ = Describe("Blocking command", func() {
var (
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
ts *httptest.Server
mockFn func(w http.ResponseWriter, _ *http.Request)
loggerHook *test.Hook
)
JustBeforeEach(func() {
ts = testHTTPAPIServer(mockFn)
@ -24,6 +28,11 @@ var _ = Describe("Blocking command", func() {
})
BeforeEach(func() {
mockFn = func(w http.ResponseWriter, _ *http.Request) {}
loggerHook = test.NewGlobal()
log.Log().AddHook(loggerHook)
})
AfterEach(func() {
loggerHook.Reset()
})
Describe("Call query command", func() {
BeforeEach(func() {
@ -56,8 +65,7 @@ var _ = Describe("Blocking command", func() {
}
})
It("should print result", func() {
query(NewQueryCommand(), []string{"google.de"})
Expect(query(NewQueryCommand(), []string{"google.de"})).Should(Succeed())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("NOERROR"))
})
})
@ -68,9 +76,9 @@ var _ = Describe("Blocking command", func() {
}
})
It("should end with error", func() {
query(NewQueryCommand(), []string{"google.de"})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("NOK: 500 Internal Server Error"))
err := query(NewQueryCommand(), []string{"google.de"})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("500 Internal Server Error"))
})
})
When("Type is wrong", func() {
@ -78,17 +86,17 @@ var _ = Describe("Blocking command", func() {
command := NewQueryCommand()
command.SetArgs([]string{"--type", "X", "google.de"})
_ = command.Execute()
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("unknown query type 'X'"))
err := command.Execute()
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("unknown query type 'X'"))
})
})
When("Url is wrong", func() {
It("should end with error", func() {
apiPort = 0
query(NewQueryCommand(), []string{"google.de"})
Expect(fatal).Should(BeTrue())
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("connection refused"))
err := query(NewQueryCommand(), []string{"google.de"})
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("connection refused"))
})
})
})

View File

@ -29,9 +29,10 @@ func NewRootCommand() *cobra.Command {
and ad-blocker for local network.
Complete documentation is available at https://github.com/0xERR0R/blocky`,
Run: func(cmd *cobra.Command, args []string) {
newServeCommand().Run(cmd, args)
RunE: func(cmd *cobra.Command, args []string) error {
return newServeCommand().RunE(cmd, args)
},
SilenceUsage: true,
}
c.PersistentFlags().StringVarP(&configPath, "config", "c", "./config.yml", "path to config file")
@ -87,7 +88,6 @@ func initConfig() {
// Execute starts the command
func Execute() {
if err := NewRootCommand().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"os"
"os/signal"
"syscall"
@ -16,7 +17,7 @@ import (
//nolint:gochecknoglobals
var (
done chan bool
done = make(chan bool, 1)
isConfigMandatory = true
)
@ -25,37 +26,49 @@ func newServeCommand() *cobra.Command {
Use: "serve",
Args: cobra.NoArgs,
Short: "start blocky DNS server (default command)",
Run: startServer,
RunE: startServer,
}
}
func startServer(_ *cobra.Command, _ []string) {
func startServer(_ *cobra.Command, _ []string) error {
printBanner()
cfg, err := config.LoadConfig(configPath, isConfigMandatory)
util.FatalOnError("unable to load configuration: ", err)
if err != nil {
return fmt.Errorf("unable to load configuration: %w", err)
}
log.ConfigureLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogTimestamp)
signals := make(chan os.Signal, 1)
done = make(chan bool, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
srv, err := server.NewServer(cfg)
util.FatalOnError("can't start server: ", err)
if err != nil {
return fmt.Errorf("can't start server: %w", err)
}
srv.Start()
errChan := make(chan error, 10)
srv.Start(errChan)
go func() {
<-signals
log.Log().Infof("Terminating...")
srv.Stop()
done <- true
select {
case <-signals:
log.Log().Infof("Terminating...")
util.LogOnError("can't stop server: ", srv.Stop())
done <- true
case <-errChan:
done <- true
}
}()
evt.Bus().Publish(evt.ApplicationStarted, util.Version, util.BuildTime)
<-done
return nil
}
func printBanner() {

View File

@ -21,7 +21,9 @@ var _ = Describe("Serve command", func() {
isConfigMandatory = false
go startServer(newServeCommand(), []string{})
go func() {
_ = startServer(newServeCommand(), []string{})
}()
time.Sleep(100 * time.Millisecond)

12
go.mod
View File

@ -29,19 +29,28 @@ require (
)
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/avast/retry-go/v4 v4.0.4
github.com/go-chi/chi/v5 v5.0.7
github.com/hashicorp/golang-lru v0.5.4
github.com/onsi/ginkgo/v2 v2.1.4
github.com/swaggo/swag v1.8.1
gorm.io/driver/postgres v1.3.5
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -56,7 +65,8 @@ require (
github.com/jackc/pgx/v4 v4.16.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect

50
go.sum
View File

@ -33,9 +33,18 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -65,6 +74,7 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -82,6 +92,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@ -95,6 +106,16 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
@ -160,6 +181,7 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -230,6 +252,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -237,6 +261,7 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -257,6 +282,10 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
@ -287,6 +316,7 @@ github.com/mroth/weightedrand v0.4.1 h1:rHcbUBopmi/3x4nnrvwGJBhX9d0vk+KgoLUZeDP6
github.com/mroth/weightedrand v0.4.1/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@ -305,6 +335,12 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -339,17 +375,21 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -363,9 +403,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -475,6 +519,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -547,6 +592,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -585,6 +631,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -717,6 +764,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@ -726,12 +774,14 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8=

View File

@ -11,7 +11,6 @@ import (
"github.com/0xERR0R/blocky/log"
"github.com/miekg/dns"
"github.com/onsi/gomega"
"github.com/onsi/gomega/types"
)
@ -113,15 +112,3 @@ func (matcher *dnsRecordMatcher) NegatedFailureMessage(actual interface{}) (mess
return fmt.Sprintf("Expected\n\t%s\n not to contain\n\t domain '%s', ttl '%d', type '%s', answer '%s'",
actual, matcher.domain, matcher.TTL, dns.TypeToString[matcher.dnsType], matcher.answer)
}
// ShouldLogFatal checks if passed function calls log.fatal
func ShouldLogFatal(fn func()) {
defer func() { log.Log().ExitFunc = nil }()
var fatal bool
log.Log().ExitFunc = func(int) { fatal = true }
fn()
gomega.Expect(fatal).Should(gomega.BeTrue())
}

View File

@ -5,6 +5,7 @@ import (
"net"
"sort"
"strings"
"sync"
"time"
"github.com/0xERR0R/blocky/cache/expirationcache"
@ -24,11 +25,11 @@ import (
"github.com/sirupsen/logrus"
)
func createBlockHandler(cfg config.BlockingConfig) blockHandler {
func createBlockHandler(cfg config.BlockingConfig) (blockHandler, error) {
cfgBlockType := cfg.BlockType
if strings.EqualFold(cfgBlockType, "NXDOMAIN") {
return nxDomainBlockHandler{}
return nxDomainBlockHandler{}, nil
}
blockTime := uint32(time.Duration(cfg.BlockTTL).Seconds())
@ -36,7 +37,7 @@ func createBlockHandler(cfg config.BlockingConfig) blockHandler {
if strings.EqualFold(cfgBlockType, "ZEROIP") {
return zeroIPBlockHandler{
BlockTimeSec: blockTime,
}
}, nil
}
var ips []net.IP
@ -54,14 +55,12 @@ func createBlockHandler(cfg config.BlockingConfig) blockHandler {
fallbackHandler: zeroIPBlockHandler{
BlockTimeSec: blockTime,
},
}
}, nil
}
log.Log().Fatalf("unknown blockType, please use one of: ZeroIP, NxDomain or specify destination IP address(es)")
return zeroIPBlockHandler{
BlockTimeSec: blockTime,
}
return nil,
fmt.Errorf("unknown blockType '%s', please use one of: ZeroIP, NxDomain or specify destination IP address(es)",
cfgBlockType)
}
type status struct {
@ -71,6 +70,7 @@ type status struct {
disabledGroups []string
enableTimer *time.Timer
disableEnd time.Time
lock sync.RWMutex
}
// BlockingResolver checks request's question (domain name) against black and white lists
@ -91,7 +91,11 @@ type BlockingResolver struct {
// NewBlockingResolver returns a new configured instance of the resolver
func NewBlockingResolver(cfg config.BlockingConfig,
redis *redis.Client, bootstrap *Bootstrap) (r ChainedResolver, err error) {
blockHandler := createBlockHandler(cfg)
blockHandler, err := createBlockHandler(cfg)
if err != nil {
return nil, err
}
refreshPeriod := time.Duration(cfg.RefreshPeriod)
downloader := lists.NewDownloader(
lists.WithTimeout(time.Duration(cfg.DownloadTimeout)),
@ -204,6 +208,8 @@ func (r *BlockingResolver) EnableBlocking() {
func (r *BlockingResolver) internalEnableBlocking() {
s := r.status
s.lock.Lock()
defer s.lock.Unlock()
s.enableTimer.Stop()
s.enabled = true
s.disabledGroups = []string{}
@ -227,8 +233,10 @@ func (r *BlockingResolver) DisableBlocking(duration time.Duration, disableGroups
func (r *BlockingResolver) internalDisableBlocking(duration time.Duration, disableGroups []string) error {
s := r.status
s.lock.Lock()
defer s.lock.Unlock()
s.enableTimer.Stop()
s.enabled = false
allBlockingGroups := r.retrieveAllBlockingGroups()
if len(disableGroups) == 0 {
@ -243,6 +251,7 @@ func (r *BlockingResolver) internalDisableBlocking(duration time.Duration, disab
s.disabledGroups = disableGroups
}
s.enabled = false
evt.Bus().Publish(evt.BlockingEnabledEvent, false)
s.disableEnd = time.Now().Add(duration)
@ -264,6 +273,10 @@ func (r *BlockingResolver) internalDisableBlocking(duration time.Duration, disab
// BlockingStatus returns the current blocking status
func (r *BlockingResolver) BlockingStatus() api.BlockingStatus {
var autoEnableDuration time.Duration
r.status.lock.RLock()
defer r.status.lock.RUnlock()
if !r.status.enabled && r.status.disableEnd.After(time.Now()) {
autoEnableDuration = time.Until(r.status.disableEnd)
}
@ -423,6 +436,9 @@ func extractEntryToCheckFromResponse(rr dns.RR) (entryToCheck string, tName stri
}
func (r *BlockingResolver) isGroupDisabled(group string) bool {
r.status.lock.RLock()
defer r.status.lock.RUnlock()
for _, g := range r.status.disabledGroups {
if g == group {
return true

View File

@ -5,7 +5,6 @@ import (
. "github.com/0xERR0R/blocky/evt"
. "github.com/0xERR0R/blocky/helpertest"
"github.com/0xERR0R/blocky/lists"
. "github.com/0xERR0R/blocky/log"
. "github.com/0xERR0R/blocky/model"
"github.com/0xERR0R/blocky/redis"
"github.com/0xERR0R/blocky/util"
@ -39,7 +38,7 @@ var _ = AfterSuite(func() {
_ = defaultGroupFile.Close()
})
var _ = Describe("BlockingResolver", func() {
var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
var (
sut *BlockingResolver
sutConfig config.BlockingConfig
@ -66,7 +65,8 @@ var _ = Describe("BlockingResolver", func() {
JustBeforeEach(func() {
m = &MockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
tmp, _ := NewBlockingResolver(sutConfig, nil, skipUpstreamCheck)
tmp, err := NewBlockingResolver(sutConfig, nil, skipUpstreamCheck)
Expect(err).Should(Succeed())
sut = tmp.(*BlockingResolver)
sut.Next(m)
sut.RefreshLists()
@ -99,7 +99,8 @@ var _ = Describe("BlockingResolver", func() {
Expect(err).Should(Succeed())
// recreate to trigger a reload
tmp, _ := NewBlockingResolver(sutConfig, nil, skipUpstreamCheck)
tmp, err := NewBlockingResolver(sutConfig, nil, skipUpstreamCheck)
Expect(err).Should(Succeed())
sut = tmp.(*BlockingResolver)
Eventually(groupCnt, "1s").Should(HaveLen(2))
@ -125,7 +126,7 @@ var _ = Describe("BlockingResolver", func() {
})
When("Full-qualified group name is used", func() {
It("bla", func() {
It("should block request", func() {
m.AnswerFn = func(t uint16, qName string) *dns.Msg {
if t == dns.TypeA && qName == "full.qualified.com." {
a, _ := util.NewMsgWithAnswer(qName, 60*60, dns.Type(dns.TypeA), "192.168.178.39")
@ -134,6 +135,7 @@ var _ = Describe("BlockingResolver", func() {
return nil
}
Bus().Publish(ApplicationStarted, "")
time.Sleep(time.Second)
Eventually(func(g Gomega) {
resp, err = sut.Resolve(newRequestWithClient("blocked2.com.", dns.Type(dns.TypeA), "192.168.178.39", "client1"))
g.Expect(err).NotTo(HaveOccurred())
@ -701,14 +703,12 @@ var _ = Describe("BlockingResolver", func() {
})
By("Wait 1 sec and perform the same query again, should be blocked now", func() {
enabled := false
enabled := make(chan bool, 1)
_ = Bus().SubscribeOnce(BlockingEnabledEvent, func(state bool) {
enabled = state
enabled <- state
})
// wait 1 sec
Eventually(func() bool {
return enabled
}, "1s").Should(BeTrue())
Eventually(enabled, "1s").Should(Receive(BeTrue()))
resp, err := sut.Resolve(newRequestWithClient("blocked3.com.", dns.Type(dns.TypeA), "1.2.1.2", "unknown"))
Expect(err).Should(Succeed())
@ -735,14 +735,14 @@ var _ = Describe("BlockingResolver", func() {
})
By("Calling Rest API to deactivate blocking for one group for 0.5 sec", func() {
enabled := true
enabled := make(chan bool, 1)
err := Bus().SubscribeOnce(BlockingEnabledEvent, func(state bool) {
enabled = state
enabled <- false
})
Expect(err).Should(Succeed())
err = sut.DisableBlocking(500*time.Millisecond, []string{"group1"})
Expect(err).Should(Succeed())
Expect(enabled).Should(BeFalse())
Eventually(enabled, "1s").Should(Receive(BeFalse()))
})
By("perform the same query again to ensure that this query will not be blocked (defaultGroup)", func() {
@ -763,14 +763,12 @@ var _ = Describe("BlockingResolver", func() {
})
By("Wait 1 sec and perform the same query again, should be blocked now", func() {
enabled := false
enabled := make(chan bool, 1)
_ = Bus().SubscribeOnce(BlockingEnabledEvent, func(state bool) {
enabled = state
enabled <- state
})
// wait 1 sec
Eventually(func() bool {
return enabled
}, "1s").Should(BeTrue())
Eventually(enabled, "1s").Should(Receive(BeTrue()))
resp, err := sut.Resolve(newRequestWithClient("blocked3.com.", dns.Type(dns.TypeA), "1.2.1.2", "unknown"))
Expect(err).Should(Succeed())
@ -847,17 +845,13 @@ var _ = Describe("BlockingResolver", func() {
Describe("Create resolver with wrong parameter", func() {
When("Wrong blockType is used", func() {
var fatal bool
It("should end with fatal exit", func() {
defer func() { Log().ExitFunc = nil }()
Log().ExitFunc = func(int) { fatal = true }
_, _ = NewBlockingResolver(config.BlockingConfig{
It("should return error", func() {
_, err := NewBlockingResolver(config.BlockingConfig{
BlockType: "wrong",
}, nil, skipUpstreamCheck)
Expect(fatal).Should(BeTrue())
Expect(err).Should(
MatchError("unknown blockType 'wrong', please use one of: ZeroIP, NxDomain or specify destination IP address(es)"))
})
})
When("failStartOnListError is active", func() {
@ -913,7 +907,7 @@ var _ = Describe("BlockingResolver", func() {
redisClient.EnabledChannel <- redisMockMsg
Eventually(func() bool {
return sut.status.enabled
return sut.BlockingStatus().Enabled
}, "5s").Should(BeFalse())
})
})
@ -927,7 +921,7 @@ var _ = Describe("BlockingResolver", func() {
redisClient.EnabledChannel <- redisMockMsg
Eventually(func() bool {
return sut.status.enabled
return sut.BlockingStatus().Enabled
}, "5s").Should(BeTrue())
})
})
@ -942,7 +936,7 @@ var _ = Describe("BlockingResolver", func() {
redisClient.EnabledChannel <- redisMockMsg
Eventually(func() bool {
return sut.status.enabled
return sut.BlockingStatus().Enabled
}, "5s").Should(BeTrue())
})
})

View File

@ -20,7 +20,7 @@ import (
. "github.com/onsi/gomega"
)
var _ = Describe("Bootstrap", func() {
var _ = Describe("Bootstrap", Label("bootstrap"), func() {
var (
sut *Bootstrap
sutConfig *config.Config
@ -52,19 +52,19 @@ var _ = Describe("Bootstrap", func() {
})
It("should use the system resolver", func() {
usedSystemResolver := false
usedSystemResolver := make(chan bool, 10)
sut.systemResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
usedSystemResolver = true
usedSystemResolver <- true
return nil, errors.New("don't actually do anything")
},
}
_, err := sut.resolveUpstream(nil, "example.com")
Expect(err).ShouldNot(Succeed())
Expect(usedSystemResolver).Should(BeTrue())
Expect(usedSystemResolver).Should(Receive(BeTrue()))
})
Describe("HTTP transport", func() {
@ -77,61 +77,63 @@ var _ = Describe("Bootstrap", func() {
})
})
When("using TCP UDP", func() {
It("accepts an IP", func() {
cfg := config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpUdp,
Host: "0.0.0.0",
Context("using TCP UDP", func() {
When("IP is set", func() {
BeforeEach(func() {
sutConfig = &config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpUdp,
Host: "0.0.0.0",
},
},
},
}
b, err := NewBootstrap(&cfg)
Expect(err).Should(Succeed())
Expect(b).ShouldNot(BeNil())
Expect(b.upstreamIPs).Should(ContainElement(net.IPv4zero))
}
})
It("accepts an IP", func() {
Expect(sut).ShouldNot(BeNil())
Expect(sut.upstreamIPs).Should(ContainElement(net.IPv4zero))
})
})
It("requires an IP", func() {
cfg := config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpUdp,
Host: "bootstrapUpstream.invalid",
When("IP is invalid", func() {
It("requires an IP", func() {
cfg := config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpUdp,
Host: "bootstrapUpstream.invalid",
},
},
},
}
}
_, err := NewBootstrap(&cfg)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring("is not an IP"))
_, err := NewBootstrap(&cfg)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring("is not an IP"))
})
})
})
When("using encrypted DNS", func() {
It("requires bootstrap IPs", func() {
cfg := config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpTls,
Host: "bootstrapUpstream.invalid",
Context("using encrypted DNS", func() {
When("IP is invalid", func() {
It("requires bootstrap IPs", func() {
cfg := config.Config{
BootstrapDNS: config.BootstrapConfig{
Upstream: config.Upstream{
Net: config.NetProtocolTcpTls,
Host: "bootstrapUpstream.invalid",
},
},
},
}
}
_, err := NewBootstrap(&cfg)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring("bootstrapDns.IPs is required"))
_, err := NewBootstrap(&cfg)
Expect(err).ShouldNot(Succeed())
Expect(err.Error()).Should(ContainSubstring("bootstrapDns.IPs is required"))
})
})
})
})
Describe("resolving", func() {
var (
bootstrapUpstream *MockResolver
)
var bootstrapUpstream *MockResolver
BeforeEach(func() {
bootstrapUpstream = &MockResolver{}
@ -224,13 +226,9 @@ var _ = Describe("Bootstrap", func() {
Log: logrus.NewEntry(log.Log()),
}
mainRes, err := util.NewMsgWithAnswer(
"example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122",
)
Expect(err).Should(Succeed())
upstream := TestUDPUpstream(func(request *dns.Msg) *dns.Msg { return mainRes })
mockUpstreamServer := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(mockUpstreamServer.Close)
upstream := mockUpstreamServer.Start()
upstreamIP := upstream.Host
@ -247,7 +245,8 @@ var _ = Describe("Bootstrap", func() {
rsp, err := r.Resolve(mainReq)
Expect(err).Should(Succeed())
Expect(rsp.Res.Id).Should(Equal(mainRes.Id))
Expect(mockUpstreamServer.GetCallCount()).Should(Equal(1))
Expect(rsp.Res.Question[0].Name).Should(Equal("example.com."))
Expect(rsp.Res.Id).ShouldNot(Equal(bootstrapResponse.Id))
})
})
@ -257,7 +256,7 @@ var _ = Describe("Bootstrap", func() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
defer server.Close()
DeferCleanup(server.Close)
url, err := url.Parse(server.URL)
Expect(err).Should(Succeed())

View File

@ -543,6 +543,7 @@ var _ = Describe("CachingResolver", func() {
},
}
redisClient.CacheChannel <- redisMockMsg
time.Sleep(time.Second)
Eventually(func() error {
resp, err = sut.Resolve(request)

View File

@ -25,7 +25,7 @@ type ClientNamesResolver struct {
}
// NewClientNamesResolver creates new resolver instance
func NewClientNamesResolver(cfg config.ClientLookupConfig, bootstrap *Bootstrap) (cr ChainedResolver, err error) {
func NewClientNamesResolver(cfg config.ClientLookupConfig, bootstrap *Bootstrap) (cr *ClientNamesResolver, err error) {
var r Resolver
if !cfg.Upstream.IsDefault() {
r, err = NewUpstreamResolver(cfg.Upstream, bootstrap)

View File

@ -2,11 +2,9 @@ package resolver
import (
"errors"
"fmt"
"net"
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/util"
. "github.com/0xERR0R/blocky/model"
@ -16,152 +14,154 @@ import (
"github.com/stretchr/testify/mock"
)
var _ = Describe("ClientResolver", func() {
var _ = Describe("ClientResolver", Label("clientNamesResolver"), func() {
var (
sut *ClientNamesResolver
sutConfig config.ClientLookupConfig
m *MockResolver
mockReverseUpstream config.Upstream
mockReverseUpstreamCallCount int
mockReverseUpstreamAnswer *dns.Msg
err error
resp *Response
sut *ClientNamesResolver
sutConfig config.ClientLookupConfig
m *MockResolver
)
BeforeEach(func() {
mockReverseUpstreamAnswer = new(dns.Msg)
mockReverseUpstreamCallCount = 0
mockReverseUpstream = TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
mockReverseUpstreamCallCount++
Expect(err).Should(Succeed())
return mockReverseUpstreamAnswer
})
sutConfig = config.ClientLookupConfig{
Upstream: mockReverseUpstream,
}
})
JustBeforeEach(func() {
tmp, _ := NewClientNamesResolver(sutConfig, skipUpstreamCheck)
sut = tmp.(*ClientNamesResolver)
res, err := NewClientNamesResolver(sutConfig, skipUpstreamCheck)
Expect(err).Should(Succeed())
sut = res
m = &MockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
sut.Next(m)
})
Describe("Resolve client name from request clientID", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{}
})
AfterEach(func() {
// next resolver will be called
m.AssertExpectations(GinkgoT())
})
It("should use clientID if set", func() {
request := newRequestWithClientID("google1.de.", dns.Type(dns.TypeA), "1.2.3.4", "client123")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("client123"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
})
It("should use IP as fallback if clientID not set", func() {
request := newRequestWithClientID("google2.de.", dns.Type(dns.TypeA), "1.2.3.4", "")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("1.2.3.4"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
})
})
Describe("Resolve client name with custom name mapping", func() {
Describe("Resolve client name with custom name mapping", Label("XXX"), func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{
Upstream: mockReverseUpstream,
ClientnameIPMapping: map[string][]net.IP{
"client7": {net.ParseIP("1.2.3.4"), net.ParseIP("1.2.3.5"), net.ParseIP("2a02:590:505:4700:2e4f:1503:ce74:df78")},
"client8": {net.ParseIP("1.2.3.5")},
"client7": {
net.ParseIP("1.2.3.4"), net.ParseIP("1.2.3.5"), net.ParseIP("2a02:590:505:4700:2e4f:1503:ce74:df78"),
},
"client8": {
net.ParseIP("1.2.3.5"),
},
},
}
})
AfterEach(func() {
// next resolver will be called
m.AssertExpectations(GinkgoT())
})
It("should resolve defined name with ipv4 address", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "1.2.3.4")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("client7"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
})
It("should resolve defined name with ipv6 address", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "2a02:590:505:4700:2e4f:1503:ce74:df78")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("client7"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
})
It("should resolve multiple names defined names", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "1.2.3.5")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(2))
Expect(request.ClientNames).Should(ContainElements("client7", "client8"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
})
})
Describe("Resolve client name via rDNS lookup", func() {
var testUpstream *MockUDPUpstreamServer
AfterEach(func() {
// next resolver will be called
m.AssertExpectations(GinkgoT())
Expect(err).Should(Succeed())
})
Context("Without order", func() {
When("Client has one name", func() {
BeforeEach(func() {
r, _ := dns.ReverseAddr("192.168.178.25")
mockReverseUpstreamAnswer, _ = util.NewMsgWithAnswer(r, 600, dns.Type(dns.TypePTR), "host1")
testUpstream = NewMockUDPUpstreamServer().
WithAnswerRR("25.178.168.192.in-addr.arpa. 600 IN PTR host1")
DeferCleanup(testUpstream.Close)
sutConfig = config.ClientLookupConfig{
Upstream: testUpstream.Start(),
}
})
It("should resolve client name", func() {
By("first request", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames[0]).Should(Equal("host1"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
By("second request", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames[0]).Should(Equal("host1"))
// use cache -> call count 1
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
// reset cache
sut.FlushCache()
By("reset cache", func() {
sut.FlushCache()
})
By("third request", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
// no cache -> call count 2
Expect(testUpstream.GetCallCount()).Should(Equal(2))
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames[0]).Should(Equal("host1"))
// no cache -> call count 2
Expect(mockReverseUpstreamCallCount).Should(Equal(2))
})
})
@ -169,24 +169,24 @@ var _ = Describe("ClientResolver", func() {
When("Client has multiple names", func() {
BeforeEach(func() {
r, _ := dns.ReverseAddr("192.168.178.25")
rr1, err := dns.NewRR(fmt.Sprintf("%s 300 IN PTR myhost1", r))
Expect(err).Should(Succeed())
rr2, err := dns.NewRR(fmt.Sprintf("%s 300 IN PTR myhost2", r))
Expect(err).Should(Succeed())
mockReverseUpstreamAnswer.Answer = []dns.RR{rr1, rr2}
testUpstream = NewMockUDPUpstreamServer().
WithAnswerRR("25.178.168.192.in-addr.arpa. 600 IN PTR myhost1", "25.178.168.192.in-addr.arpa. 600 IN PTR myhost2")
DeferCleanup(testUpstream.Close)
sutConfig = config.ClientLookupConfig{
Upstream: testUpstream.Start(),
}
})
It("should resolve all client names", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(2))
Expect(request.ClientNames[0]).Should(Equal("myhost1"))
Expect(request.ClientNames[1]).Should(Equal("myhost2"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
})
@ -194,113 +194,132 @@ var _ = Describe("ClientResolver", func() {
Context("with order", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{
Upstream: mockReverseUpstream,
SingleNameOrder: []uint{2, 1}}
SingleNameOrder: []uint{2, 1},
}
})
When("Client has one name", func() {
BeforeEach(func() {
r, _ := dns.ReverseAddr("192.168.178.25")
mockReverseUpstreamAnswer, _ = util.NewMsgWithAnswer(r, 600, dns.Type(dns.TypePTR), "host1")
testUpstream = NewMockUDPUpstreamServer().
WithAnswerRR("25.178.168.192.in-addr.arpa. 600 IN PTR host1")
DeferCleanup(testUpstream.Close)
sutConfig.Upstream = testUpstream.Start()
})
It("should resolve client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames[0]).Should(Equal("host1"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
})
When("Client has multiple names", func() {
BeforeEach(func() {
r, _ := dns.ReverseAddr("192.168.178.25")
rr1, err := dns.NewRR(fmt.Sprintf("%s 300 IN PTR myhost1", r))
Expect(err).Should(Succeed())
rr2, err := dns.NewRR(fmt.Sprintf("%s 300 IN PTR myhost2", r))
Expect(err).Should(Succeed())
mockReverseUpstreamAnswer.Answer = []dns.RR{rr1, rr2}
testUpstream = NewMockUDPUpstreamServer().
WithAnswerRR("25.178.168.192.in-addr.arpa. 600 IN PTR myhost1", "25.178.168.192.in-addr.arpa. 600 IN PTR myhost2")
DeferCleanup(testUpstream.Close)
sutConfig.Upstream = testUpstream.Start()
})
It("should resolve the client name depending to defined order", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("myhost2"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
})
})
When("Upstream can't resolve client name via rDNS", func() {
BeforeEach(func() {
mockReverseUpstreamAnswer.Rcode = dns.RcodeNameError
})
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
Context("Error cases", func() {
When("Upstream can't resolve client name via rDNS", func() {
BeforeEach(func() {
testUpstream = NewMockUDPUpstreamServer().
WithAnswerError(dns.RcodeNameError)
DeferCleanup(testUpstream.Close)
sutConfig = config.ClientLookupConfig{
Upstream: testUpstream.Start(),
}
})
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
Expect(mockReverseUpstreamCallCount).Should(Equal(1))
})
})
When("Upstream produces error", func() {
JustBeforeEach(func() {
clientMockResolver := &MockResolver{}
clientMockResolver.On("Resolve", mock.Anything).Return(nil, errors.New("error"))
sut.externalResolver = clientMockResolver
})
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
Expect(testUpstream.GetCallCount()).Should(Equal(1))
})
})
})
When("Upstream produces error", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{}
clientMockResolver := &MockResolver{}
clientMockResolver.On("Resolve", mock.Anything).Return(nil, errors.New("error"))
sut.externalResolver = clientMockResolver
})
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
When("Client has no IP", func() {
It("should resolve no names", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "")
resp, err = sut.Resolve(request)
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(BeEmpty())
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
})
})
})
When("No upstream is defined", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{}
When("Client has no IP", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{}
})
It("should resolve no names", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "")
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(BeEmpty())
})
})
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err = sut.Resolve(request)
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
Expect(mockReverseUpstreamCallCount).Should(Equal(0))
When("No upstream is defined", func() {
BeforeEach(func() {
sutConfig = config.ClientLookupConfig{}
})
It("should use fallback for client name", func() {
request := newRequestWithClient("google.de.", dns.Type(dns.TypeA), "192.168.178.25")
resp, err := sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(request.ClientNames).Should(HaveLen(1))
Expect(request.ClientNames[0]).Should(Equal("192.168.178.25"))
})
})
})
})
Describe("Connstruction", func() {
When("upstream is invalid", func() {
It("errors during construction", func() {
b := TestBootstrap(&dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}})
When("upstream is invalid", func() {
It("errors during construction", func() {
b := TestBootstrap(&dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}})
r, err := NewClientNamesResolver(config.ClientLookupConfig{
Upstream: config.Upstream{Host: "example.com"},
}, b)
r, err := NewClientNamesResolver(config.ClientLookupConfig{
Upstream: config.Upstream{Host: "example.com"},
}, b)
Expect(err).ShouldNot(Succeed())
Expect(r).Should(BeNil())
Expect(err).ShouldNot(Succeed())
Expect(r).Should(BeNil())
})
})
})
@ -331,6 +350,6 @@ var _ = Describe("ClientResolver", func() {
Expect(c).Should(Equal([]string{"deactivated, use only IP address"}))
})
})
})
})
})

View File

@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/mock"
)
var _ = Describe("ConditionalUpstreamResolver", func() {
var _ = Describe("ConditionalUpstreamResolver", Label("conditionalResolver"), func() {
var (
sut ChainedResolver
m *MockResolver
@ -27,24 +27,33 @@ var _ = Describe("ConditionalUpstreamResolver", func() {
})
BeforeEach(func() {
fbTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.Type(dns.TypeA), "123.124.122.122")
return response
})
DeferCleanup(fbTestUpstream.Close)
otherTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 250, dns.Type(dns.TypeA), "192.192.192.192")
return response
})
DeferCleanup(otherTestUpstream.Close)
dotTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 223, dns.Type(dns.TypeA), "168.168.168.168")
return response
})
DeferCleanup(dotTestUpstream.Close)
sut, _ = NewConditionalUpstreamResolver(config.ConditionalUpstreamConfig{
Mapping: config.ConditionalUpstreamMapping{
Upstreams: map[string][]config.Upstream{
"fritz.box": {TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.Type(dns.TypeA), "123.124.122.122")
return response
})},
"other.box": {TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 250, dns.Type(dns.TypeA), "192.192.192.192")
return response
})},
".": {TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 223, dns.Type(dns.TypeA), "168.168.168.168")
return response
})},
"fritz.box": {fbTestUpstream.Start()},
"other.box": {otherTestUpstream.Start()},
".": {dotTestUpstream.Start()},
}},
}, skipUpstreamCheck)
m = &MockResolver{}

View File

@ -7,6 +7,7 @@ import (
"net/http/httptest"
"strconv"
"strings"
"sync/atomic"
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/util"
@ -133,15 +134,76 @@ func TestDOHUpstream(fn func(request *dns.Msg) (response *dns.Msg),
return upstream
}
// TestUDPUpstream creates a mock UDP upstream
//nolint:funlen
func TestUDPUpstream(fn func(request *dns.Msg) (response *dns.Msg)) config.Upstream {
type MockUDPUpstreamServer struct {
callCount int32
ln *net.UDPConn
answerFn func(request *dns.Msg) (response *dns.Msg)
}
func NewMockUDPUpstreamServer() *MockUDPUpstreamServer {
return &MockUDPUpstreamServer{}
}
func (t *MockUDPUpstreamServer) WithAnswerRR(answers ...string) *MockUDPUpstreamServer {
t.answerFn = func(request *dns.Msg) (response *dns.Msg) {
msg := new(dns.Msg)
for _, a := range answers {
rr, err := dns.NewRR(a)
util.FatalOnError("can't create RR", err)
msg.Answer = append(msg.Answer, rr)
}
return msg
}
return t
}
func (t *MockUDPUpstreamServer) WithAnswerMsg(answer *dns.Msg) *MockUDPUpstreamServer {
t.answerFn = func(request *dns.Msg) (response *dns.Msg) {
return answer
}
return t
}
func (t *MockUDPUpstreamServer) WithAnswerError(errorCode int) *MockUDPUpstreamServer {
t.answerFn = func(request *dns.Msg) (response *dns.Msg) {
msg := new(dns.Msg)
msg.Rcode = errorCode
return msg
}
return t
}
func (t *MockUDPUpstreamServer) WithAnswerFn(fn func(request *dns.Msg) (response *dns.Msg)) *MockUDPUpstreamServer {
t.answerFn = fn
return t
}
func (t *MockUDPUpstreamServer) GetCallCount() int {
return int(atomic.LoadInt32(&t.callCount))
}
func (t *MockUDPUpstreamServer) Close() {
if t.ln != nil {
_ = t.ln.Close()
}
}
func (t *MockUDPUpstreamServer) Start() config.Upstream {
a, err := net.ResolveUDPAddr("udp4", ":0")
util.FatalOnError("can't resolve address: ", err)
ln, err := net.ListenUDP("udp4", a)
util.FatalOnError("can't create connection: ", err)
t.ln = ln
ladr := ln.LocalAddr().String()
host := strings.Split(ladr, ":")[0]
p, err := strconv.ParseUint(strings.Split(ladr, ":")[1], 10, 16)
@ -154,14 +216,20 @@ func TestUDPUpstream(fn func(request *dns.Msg) (response *dns.Msg)) config.Upstr
for {
buffer := make([]byte, 1024)
n, addr, err := ln.ReadFromUDP(buffer)
util.FatalOnError("error on reading from udp: ", err)
if err != nil {
// closed
break
}
msg := new(dns.Msg)
err = msg.Unpack(buffer[0 : n-1])
util.FatalOnError("can't deserialize message: ", err)
response := fn(msg)
var response = t.answerFn(msg)
atomic.AddInt32(&t.callCount, 1)
// nil should indicate an error
if response == nil {
_, _ = ln.WriteToUDP([]byte("dummy"), addr)
@ -179,7 +247,10 @@ func TestUDPUpstream(fn func(request *dns.Msg) (response *dns.Msg)) config.Upstr
util.FatalOnError("can't serialize message: ", err)
_, err = ln.WriteToUDP(b, addr)
util.FatalOnError("can't write to UDP: ", err)
if err != nil {
// closed
break
}
}
}()

View File

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"strings"
"sync/atomic"
"time"
"github.com/0xERR0R/blocky/config"
@ -15,9 +16,8 @@ import (
)
const (
upstreamDefaultCfgNameDeprecated = "externalResolvers"
upstreamDefaultCfgName = "default"
parallelResolverLogger = "parallel_best_resolver"
upstreamDefaultCfgName = "default"
parallelResolverLogger = "parallel_best_resolver"
)
// ParallelBestResolver delegates the DNS message to 2 upstream resolvers and returns the fastest answer
@ -27,7 +27,7 @@ type ParallelBestResolver struct {
type upstreamResolverStatus struct {
resolver Resolver
lastErrorTime time.Time
lastErrorTime atomic.Value
}
type requestResponse struct {
@ -38,7 +38,6 @@ type requestResponse struct {
// NewParallelBestResolver creates new resolver instance
func NewParallelBestResolver(upstreamResolvers map[string][]config.Upstream, bootstrap *Bootstrap) (Resolver, error) {
s := make(map[string][]*upstreamResolverStatus, len(upstreamResolvers))
logger := logger(parallelResolverLogger)
for name, res := range upstreamResolvers {
resolvers := make([]*upstreamResolverStatus, len(res))
@ -50,24 +49,16 @@ func NewParallelBestResolver(upstreamResolvers map[string][]config.Upstream, boo
}
resolvers[i] = &upstreamResolverStatus{
resolver: r,
lastErrorTime: time.Unix(0, 0),
resolver: r,
}
}
if _, ok := upstreamResolvers[upstreamDefaultCfgName]; !ok && name == upstreamDefaultCfgNameDeprecated {
logger.Warnf("using deprecated '%s' as default upstream resolver"+
" configuration name, please consider to change it to '%s'",
upstreamDefaultCfgNameDeprecated, upstreamDefaultCfgName)
name = upstreamDefaultCfgName
resolvers[i].lastErrorTime.Store(time.Unix(0, 0))
}
s[name] = resolvers
}
if len(s[upstreamDefaultCfgName]) == 0 {
logger.Fatalf("no external DNS resolvers configured as default upstream resolvers. "+
return nil, fmt.Errorf("no external DNS resolvers configured as default upstream resolvers. "+
"Please configure at least one under '%s' configuration name", upstreamDefaultCfgName)
}
@ -195,9 +186,9 @@ func weightedRandom(in []*upstreamResolverStatus, exclude Resolver) *upstreamRes
for _, res := range in {
var weight float64 = 60
if time.Since(res.lastErrorTime) < time.Hour {
if time.Since(res.lastErrorTime.Load().(time.Time)) < time.Hour {
// reduce weight: consider last error time
weight = math.Max(1, weight-(60-time.Since(res.lastErrorTime).Minutes()))
weight = math.Max(1, weight-(60-time.Since(res.lastErrorTime.Load().(time.Time)).Minutes()))
}
if exclude != res.resolver {
@ -218,7 +209,7 @@ func resolve(req *model.Request, resolver *upstreamResolverStatus, ch chan<- req
// update the last error time
if err != nil {
resolver.lastErrorTime = time.Now()
resolver.lastErrorTime.Store(time.Now())
}
ch <- requestResponse{
response: resp,

View File

@ -6,7 +6,6 @@ import (
"github.com/0xERR0R/blocky/config"
. "github.com/0xERR0R/blocky/helpertest"
. "github.com/0xERR0R/blocky/log"
. "github.com/0xERR0R/blocky/model"
"github.com/0xERR0R/blocky/util"
"github.com/miekg/dns"
@ -14,46 +13,44 @@ import (
. "github.com/onsi/gomega"
)
var _ = Describe("ParallelBestResolver", func() {
var (
sut Resolver
err error
resp *Response
)
var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
config.GetConfig().UpstreamTimeout = config.Duration(1000 * time.Millisecond)
Describe("Default upstream resolvers are not defined", func() {
It("should fail on startup", func() {
defer func() { Log().ExitFunc = nil }()
var fatal bool
Log().ExitFunc = func(int) { fatal = true }
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{}, skipUpstreamCheck)
Expect(fatal).Should(BeTrue())
_, err := NewParallelBestResolver(map[string][]config.Upstream{}, skipUpstreamCheck)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("no external DNS resolvers configured"))
})
})
Describe("Resolving result from fastest upstream resolver", func() {
var (
sut Resolver
err error
resp *Response
)
When("2 Upstream resolvers are defined", func() {
When("one resolver is fast and another is slow", func() {
BeforeEach(func() {
fast := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122")
fastTestUpstream := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(fastTestUpstream.Close)
Expect(err).Should(Succeed())
return response
})
slow := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
slowTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.123")
time.Sleep(50 * time.Millisecond)
Expect(err).Should(Succeed())
return response
})
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {fast, slow},
DeferCleanup(slowTestUpstream.Close)
sut, err = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {fastTestUpstream.Start(), slowTestUpstream.Start()},
}, skipUpstreamCheck)
Expect(err).Should(Succeed())
})
It("Should use result from fastest one", func() {
request := newRequest("example.com.", dns.Type(dns.TypeA))
@ -68,18 +65,19 @@ var _ = Describe("ParallelBestResolver", func() {
})
When("one resolver is slow, but another returns an error", func() {
BeforeEach(func() {
withError := config.Upstream{Host: "wrong"}
slow := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
withErrorUpstream := config.Upstream{Host: "wrong"}
slowTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.123")
time.Sleep(50 * time.Millisecond)
Expect(err).Should(Succeed())
return response
})
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {withError, slow},
DeferCleanup(slowTestUpstream.Close)
sut, err = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {withErrorUpstream, slowTestUpstream.Start()},
}, skipUpstreamCheck)
Expect(err).Should(Succeed())
})
It("Should use result from successful resolver", func() {
request := newRequest("example.com.", dns.Type(dns.TypeA))
@ -97,9 +95,10 @@ var _ = Describe("ParallelBestResolver", func() {
withError1 := config.Upstream{Host: "wrong"}
withError2 := config.Upstream{Host: "wrong"}
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{
sut, err = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {withError1, withError2},
}, skipUpstreamCheck)
Expect(err).Should(Succeed())
})
It("Should return error", func() {
request := newRequest("example.com.", dns.Type(dns.TypeA))
@ -113,50 +112,41 @@ var _ = Describe("ParallelBestResolver", func() {
When("client specific resolvers are defined", func() {
When("client name matches", func() {
BeforeEach(func() {
defaultResolver := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122")
Expect(err).Should(Succeed())
return response
})
clientSpecificResolverExact := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.123")
defaultMockUpstream := NewMockUDPUpstreamServer().
WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(defaultMockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
clientSpecificResolverWildcard := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.124")
time.Sleep(50 * time.Millisecond)
clientSpecificExactMockUpstream := NewMockUDPUpstreamServer().
WithAnswerRR("example.com 123 IN A 123.124.122.123")
DeferCleanup(clientSpecificExactMockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
clientSpecificResolverIP := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.125")
clientSpecificWildcardMockUpstream := NewMockUDPUpstreamServer().
WithAnswerRR("example.com 123 IN A 123.124.122.124")
DeferCleanup(clientSpecificWildcardMockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
clientSpecificResolverCIDR := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.126")
clientSpecificIPMockUpstream := NewMockUDPUpstreamServer().
WithAnswerRR("example.com 123 IN A 123.124.122.125")
DeferCleanup(clientSpecificIPMockUpstream.Close)
clientSpecificCIRDMockUpstream := NewMockUDPUpstreamServer().
WithAnswerRR("example.com 123 IN A 123.124.122.126")
DeferCleanup(clientSpecificCIRDMockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgNameDeprecated: {defaultResolver},
"laptop": {clientSpecificResolverExact},
"client-*-m": {clientSpecificResolverWildcard},
"client[0-9]": {clientSpecificResolverWildcard},
"192.168.178.33": {clientSpecificResolverIP},
"10.43.8.67/28": {clientSpecificResolverCIDR},
upstreamDefaultCfgName: {defaultMockUpstream.Start()},
"laptop": {clientSpecificExactMockUpstream.Start()},
"client-*-m": {clientSpecificWildcardMockUpstream.Start()},
"client[0-9]": {clientSpecificWildcardMockUpstream.Start()},
"192.168.178.33": {clientSpecificIPMockUpstream.Start()},
"10.43.8.67/28": {clientSpecificCIRDMockUpstream.Start()},
}, skipUpstreamCheck)
})
It("Should use default if client name or IP don't match", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.55", "test")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.122"))
@ -165,6 +155,7 @@ var _ = Describe("ParallelBestResolver", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.55", "laptop")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.123"))
@ -173,6 +164,7 @@ var _ = Describe("ParallelBestResolver", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.55", "client-test-m")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.124"))
@ -181,6 +173,7 @@ var _ = Describe("ParallelBestResolver", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.55", "client7")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.124"))
@ -189,6 +182,7 @@ var _ = Describe("ParallelBestResolver", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.33", "cl")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.125"))
@ -197,6 +191,7 @@ var _ = Describe("ParallelBestResolver", func() {
request := newRequestWithClient("example.com.", dns.Type(dns.TypeA), "10.43.8.64", "cl")
resp, err = sut.Resolve(request)
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeDNSRecord("example.com.", dns.TypeA, 123, "123.124.122.126"))
@ -205,13 +200,14 @@ var _ = Describe("ParallelBestResolver", func() {
})
When("only 1 upstream resolvers is defined", func() {
BeforeEach(func() {
fast := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122")
mockUpstream := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(mockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{upstreamDefaultCfgName: {fast}}, skipUpstreamCheck)
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {
mockUpstream.Start(),
},
}, skipUpstreamCheck)
})
It("Should use result from defined resolver", func() {
request := newRequest("example.com.", dns.Type(dns.TypeA))
@ -229,21 +225,15 @@ var _ = Describe("ParallelBestResolver", func() {
It("should use 2 random peeked resolvers, weighted with last error timestamp", func() {
withError1 := config.Upstream{Host: "wrong1"}
withError2 := config.Upstream{Host: "wrong2"}
fast1 := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122")
Expect(err).Should(Succeed())
return response
})
fast2 := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com.", 123, dns.Type(dns.TypeA), "123.124.122.122")
mockUpstream1 := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(mockUpstream1.Close)
Expect(err).Should(Succeed())
return response
})
mockUpstream2 := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(mockUpstream2.Close)
tmp, _ := NewParallelBestResolver(map[string][]config.Upstream{
upstreamDefaultCfgName: {withError1, fast1, fast2, withError2},
upstreamDefaultCfgName: {withError1, mockUpstream1.Start(), mockUpstream2.Start(), withError2},
}, skipUpstreamCheck)
sut := tmp.(*ParallelBestResolver)
@ -314,6 +304,9 @@ var _ = Describe("ParallelBestResolver", func() {
})
Describe("Configuration output", func() {
var (
sut Resolver
)
BeforeEach(func() {
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{upstreamDefaultCfgName: {
{Host: "host1"},

View File

@ -5,6 +5,7 @@ import (
"testing"
"github.com/0xERR0R/blocky/log"
"github.com/go-redis/redis/v8"
. "github.com/onsi/ginkgo/v2"
@ -20,4 +21,4 @@ func TestResolver(t *testing.T) {
type NoLogs struct{}
func (l NoLogs) Printf(ctx context.Context, format string, v ...interface{}) {}
func (l NoLogs) Printf(_ context.Context, _ string, _ ...interface{}) {}

View File

@ -4,6 +4,7 @@ import (
"crypto/tls"
"fmt"
"net/http"
"sync/atomic"
"time"
"github.com/0xERR0R/blocky/config"
@ -22,16 +23,15 @@ func init() {
skipUpstreamCheck = &Bootstrap{}
}
var _ = Describe("UpstreamResolver", func() {
var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
Describe("Using DNS upstream", func() {
When("Configured DNS resolver can resolve query", func() {
It("should return answer from DNS upstream", func() {
upstream := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
mockUpstream := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
DeferCleanup(mockUpstream.Close)
Expect(err).Should(Succeed())
return response
})
upstream := mockUpstream.Start()
sut, _ := NewUpstreamResolver(upstream, skipUpstreamCheck)
resp, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
@ -44,12 +44,10 @@ var _ = Describe("UpstreamResolver", func() {
})
When("Configured DNS resolver can't resolve query", func() {
It("should return response code from DNS upstream", func() {
upstream := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
response := new(dns.Msg)
response.SetRcode(request, dns.RcodeNameError)
mockUpstream := NewMockUDPUpstreamServer().WithAnswerError(dns.RcodeNameError)
DeferCleanup(mockUpstream.Close)
return response
})
upstream := mockUpstream.Start()
sut, _ := NewUpstreamResolver(upstream, skipUpstreamCheck)
resp, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
@ -61,10 +59,11 @@ var _ = Describe("UpstreamResolver", func() {
})
When("Configured DNS resolver fails", func() {
It("should return error", func() {
upstream := TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
mockUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
return nil
})
DeferCleanup(mockUpstream.Close)
upstream := mockUpstream.Start()
sut, _ := NewUpstreamResolver(upstream, skipUpstreamCheck)
_, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
@ -72,26 +71,33 @@ var _ = Describe("UpstreamResolver", func() {
})
})
When("Timeout occurs", func() {
counter := 0
attemptsWithTimeout := 2
upstream := TestUDPUpstream(func(request *dns.Msg) (response *dns.Msg) {
counter++
// timeout on first x attempts
if counter <= attemptsWithTimeout {
time.Sleep(110 * time.Millisecond)
var counter int32
var attemptsWithTimeout int32
var sut *UpstreamResolver
BeforeEach(func() {
resolveFn := func(request *dns.Msg) (response *dns.Msg) {
atomic.AddInt32(&counter, 1)
// timeout on first x attempts
if atomic.LoadInt32(&counter) <= atomic.LoadInt32(&attemptsWithTimeout) {
time.Sleep(110 * time.Millisecond)
}
response, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
Expect(err).Should(Succeed())
return response
}
response, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
Expect(err).Should(Succeed())
mockUpstream := NewMockUDPUpstreamServer().WithAnswerFn(resolveFn)
DeferCleanup(mockUpstream.Close)
return response
upstream := mockUpstream.Start()
sut, _ = NewUpstreamResolver(upstream, skipUpstreamCheck)
sut.upstreamClient.(*dnsUpstreamClient).udpClient.Timeout = 100 * time.Millisecond
})
sut, _ := NewUpstreamResolver(upstream, skipUpstreamCheck)
sut.upstreamClient.(*dnsUpstreamClient).udpClient.Timeout = 100 * time.Millisecond
It("should perform a retry with 3 attempts", func() {
By("2 attempts with timeout -> should resolve with third attempt", func() {
counter = 0
attemptsWithTimeout = 2
atomic.StoreInt32(&counter, 0)
atomic.StoreInt32(&attemptsWithTimeout, 2)
resp, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
Expect(err).Should(Succeed())
@ -101,8 +107,8 @@ var _ = Describe("UpstreamResolver", func() {
})
By("3 attempts with timeout -> should return error", func() {
attemptsWithTimeout = 3
counter = 0
atomic.StoreInt32(&counter, 0)
atomic.StoreInt32(&attemptsWithTimeout, 3)
_, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(ContainSubstring("i/o timeout"))

View File

@ -47,13 +47,16 @@ func getServerAddress(addr string) string {
return addr
}
type NewServerFunc func(address string) *dns.Server
type NewServerFunc func(address string) (*dns.Server, error)
// NewServer creates new server instance with passed config
func NewServer(cfg *config.Config) (server *Server, err error) {
log.ConfigureLogger(cfg.LogLevel, cfg.LogFormat, cfg.LogTimestamp)
dnsServers := createServers(cfg)
dnsServers, err := createServers(cfg)
if err != nil {
return nil, fmt.Errorf("server creation failed: %w", err)
}
router := createRouter(cfg)
@ -102,21 +105,32 @@ func NewServer(cfg *config.Config) (server *Server, err error) {
return server, err
}
func createServers(cfg *config.Config) (dnsServers []*dns.Server) {
addServers := func(newServer NewServerFunc, addresses config.ListenConfig) {
func createServers(cfg *config.Config) ([]*dns.Server, error) {
var dnsServers []*dns.Server
var err *multierror.Error
addServers := func(newServer NewServerFunc, addresses config.ListenConfig) error {
for _, address := range addresses {
dnsServers = append(dnsServers, newServer(getServerAddress(address)))
server, err := newServer(getServerAddress(address))
if err != nil {
return err
}
dnsServers = append(dnsServers, server)
}
return nil
}
addServers(createUDPServer, cfg.DNSPorts)
addServers(createTCPServer, cfg.DNSPorts)
err = multierror.Append(err,
addServers(createUDPServer, cfg.DNSPorts),
addServers(createTCPServer, cfg.DNSPorts),
addServers(func(address string) (*dns.Server, error) {
return createTLSServer(address, cfg.CertFile, cfg.KeyFile)
}, cfg.TLSPorts))
addServers(func(address string) *dns.Server {
return createTLSServer(address, cfg.CertFile, cfg.KeyFile)
}, cfg.TLSPorts)
return
return dnsServers, err.ErrorOrNil()
}
func createHTTPListeners(cfg *config.Config) (httpListeners []net.Listener, httpsListeners []net.Listener, err error) {
@ -160,9 +174,11 @@ func registerResolverAPIEndpoints(router chi.Router, res resolver.Resolver) {
}
}
func createTLSServer(address string, certFile string, keyFile string) *dns.Server {
func createTLSServer(address string, certFile string, keyFile string) (*dns.Server, error) {
cer, err := tls.LoadX509KeyPair(certFile, keyFile)
util.FatalOnError("can't load certificate files: ", err)
if err != nil {
return nil, fmt.Errorf("can't load certificate files: %w", err)
}
return &dns.Server{
Addr: address,
@ -175,10 +191,10 @@ func createTLSServer(address string, certFile string, keyFile string) *dns.Serve
NotifyStartedFunc: func() {
logger().Infof("TLS server is up and running on address %s", address)
},
}
}, nil
}
func createTCPServer(address string) *dns.Server {
func createTCPServer(address string) (*dns.Server, error) {
return &dns.Server{
Addr: address,
Net: "tcp",
@ -186,10 +202,10 @@ func createTCPServer(address string) *dns.Server {
NotifyStartedFunc: func() {
logger().Infof("TCP server is up and running on address %s", address)
},
}
}, nil
}
func createUDPServer(address string) *dns.Server {
func createUDPServer(address string) (*dns.Server, error) {
return &dns.Server{
Addr: address,
Net: "udp",
@ -198,7 +214,7 @@ func createUDPServer(address string) *dns.Server {
logger().Infof("UDP server is up and running on address %s", address)
},
UDPSize: 65535,
}
}, nil
}
func createQueryResolver(
@ -292,7 +308,7 @@ func toMB(b uint64) uint64 {
}
// Start starts the server
func (s *Server) Start() {
func (s *Server) Start(errCh chan<- error) {
logger().Info("Starting server")
for _, srv := range s.dnsServers {
@ -300,7 +316,7 @@ func (s *Server) Start() {
go func() {
if err := srv.ListenAndServe(); err != nil {
logger().Fatalf("start %s listener failed: %v", srv.Net, err)
errCh <- fmt.Errorf("start %s listener failed: %w", srv.Net, err)
}
}()
}
@ -312,8 +328,9 @@ func (s *Server) Start() {
go func() {
logger().Infof("http server is up and running on addr/port %s", address)
err := http.Serve(listener, s.httpMux)
util.FatalOnError("start http listener failed: ", err)
if err := http.Serve(listener, s.httpMux); err != nil {
errCh <- fmt.Errorf("start http listener failed: %w", err)
}
}()
}
@ -324,8 +341,9 @@ func (s *Server) Start() {
go func() {
logger().Infof("https server is up and running on addr/port %s", address)
err := http.ServeTLS(listener, s.httpMux, s.cfg.CertFile, s.cfg.KeyFile)
util.FatalOnError("start https listener failed: ", err)
if err := http.ServeTLS(listener, s.httpMux, s.cfg.CertFile, s.cfg.KeyFile); err != nil {
errCh <- fmt.Errorf("start https listener failed: %w", err)
}
}()
}
@ -333,14 +351,16 @@ func (s *Server) Start() {
}
// Stop stops the server
func (s *Server) Stop() {
func (s *Server) Stop() error {
logger().Info("Stopping server")
for _, server := range s.dnsServers {
if err := server.Shutdown(); err != nil {
logger().Fatalf("stop %s listener failed: %v", server.Net, err)
return fmt.Errorf("stop %s listener failed: %w", server.Net, err)
}
}
return nil
}
func createResolverRequest(rw dns.ResponseWriter, request *dns.Msg) *model.Request {

View File

@ -8,6 +8,7 @@ import (
"net"
"net/http"
"strings"
"sync/atomic"
"time"
"github.com/0xERR0R/blocky/api"
@ -26,15 +27,15 @@ import (
)
var (
upstreamGoogle, upstreamFritzbox, upstreamClient config.Upstream
mockClientName string
sut *Server
err error
resp *dns.Msg
mockClientName atomic.Value
sut *Server
err error
resp *dns.Msg
)
var _ = BeforeSuite(func() {
upstreamGoogle = resolver.TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
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
}
@ -45,7 +46,9 @@ var _ = BeforeSuite(func() {
Expect(err).Should(Succeed())
return response
})
upstreamFritzbox = resolver.TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
DeferCleanup(googleMockUpstream.Close)
fritzboxMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, err := util.NewMsgWithAnswer(
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypeA), "192.168.178.2",
)
@ -53,15 +56,21 @@ var _ = BeforeSuite(func() {
Expect(err).Should(Succeed())
return response
})
DeferCleanup(fritzboxMockUpstream.Close)
upstreamClient = resolver.TestUDPUpstream(func(request *dns.Msg) *dns.Msg {
clientMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, err := util.NewMsgWithAnswer(
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), mockClientName,
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), mockClientName.Load().(string),
)
Expect(err).Should(Succeed())
return response
})
DeferCleanup(clientMockUpstream.Close)
upstreamClient = clientMockUpstream.Start()
upstreamFritzbox = fritzboxMockUpstream.Start()
upstreamGoogle = googleMockUpstream.Start()
// create server
sut, err = NewServer(&config.Config{
@ -123,22 +132,22 @@ var _ = BeforeSuite(func() {
Expect(err).Should(Succeed())
errChan := make(chan error, 10)
// start server
go func() {
sut.Start()
sut.Start(errChan)
}()
time.Sleep(100 * time.Millisecond)
})
DeferCleanup(sut.Stop)
var _ = AfterSuite(func() {
sut.Stop()
Consistently(errChan, "1s").ShouldNot(Receive())
})
var _ = Describe("Running DNS server", func() {
Describe("performing DNS request with running server", func() {
BeforeEach(func() {
mockClientName = ""
mockClientName.Store("")
// reset client cache
res := sut.queryResolver
for res != nil {
@ -222,7 +231,7 @@ var _ = Describe("Running DNS server", func() {
})
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 = "clWhitelistOnly"
mockClientName.Store("clWhitelistOnly")
resp = requestServer(util.NewMsgWithQuestion("heise.de.", dns.Type(dns.TypeA)))
Expect(resp.Answer).Should(BeDNSRecord("heise.de.", dns.TypeA, 0, "123.124.122.122"))
@ -230,7 +239,7 @@ var _ = Describe("Running DNS server", func() {
})
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 = "clWhitelistOnly"
mockClientName.Store("clWhitelistOnly")
resp = requestServer(util.NewMsgWithQuestion("google.de.", dns.Type(dns.TypeA)))
Expect(resp.Answer).Should(BeDNSRecord("google.de.", dns.TypeA, 0, "0.0.0.0"))
@ -238,7 +247,7 @@ var _ = Describe("Running DNS server", func() {
})
Context("block client with 2 groups", func() {
It("Query with should be blocked, domain is on black list", func() {
mockClientName = "clAdsAndYoutube"
mockClientName.Store("clAdsAndYoutube")
resp = requestServer(util.NewMsgWithQuestion("www.bild.de.", dns.Type(dns.TypeA)))
Expect(resp.Answer).Should(BeDNSRecord("www.bild.de.", dns.TypeA, 0, "0.0.0.0"))
@ -250,7 +259,7 @@ var _ = Describe("Running DNS server", func() {
})
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 = "clYoutubeOnly"
mockClientName.Store("clYoutubeOnly")
resp = requestServer(util.NewMsgWithQuestion("www.bild.de.", dns.Type(dns.TypeA)))
Expect(resp.Answer).Should(BeDNSRecord("www.bild.de.", dns.TypeA, 0, "123.124.122.122"))
@ -258,7 +267,7 @@ var _ = Describe("Running DNS server", func() {
})
Context("block client with 1 group", func() {
It("Query with should not blocked, domain is on black list in client's group", func() {
mockClientName = "clYoutubeOnly"
mockClientName.Store("clYoutubeOnly")
resp = requestServer(util.NewMsgWithQuestion("youtube.com.", dns.Type(dns.TypeA)))
Expect(resp.Answer).Should(BeDNSRecord("youtube.com.", dns.TypeA, 0, "0.0.0.0"))
@ -515,14 +524,11 @@ var _ = Describe("Running DNS server", func() {
})
When("Server is created", func() {
It("is created without redis connection", func() {
defer func() { Log().ExitFunc = nil }()
_, err := NewServer(&cfg)
Expect(err).Should(Succeed())
})
It("can't be created if redis server is unavailable", func() {
defer func() { Log().ExitFunc = nil }()
cfg.Redis.Required = true
@ -533,15 +539,9 @@ var _ = Describe("Running DNS server", func() {
})
})
Describe("Server start", func() {
Describe("Server start", Label("XX"), func() {
When("Server start is called", func() {
It("start was called 2 times, start should fail", func() {
defer func() { Log().ExitFunc = nil }()
var fatal bool
Log().ExitFunc = func(int) { fatal = true }
// create server
server, err := NewServer(&config.Config{
Upstream: config.UpstreamConfig{
@ -560,37 +560,27 @@ var _ = Describe("Running DNS server", func() {
Expect(err).Should(Succeed())
errChan := make(chan error, 10)
// start server
go func() {
server.Start()
server.Start(errChan)
}()
defer server.Stop()
DeferCleanup(server.Stop)
Eventually(func() bool {
return fatal
}, "100ms").Should(BeFalse())
Expect(fatal).Should(BeFalse())
Consistently(errChan, "1s").ShouldNot(Receive())
// start again -> should fail
server.Start()
server.Start(errChan)
Eventually(func() bool {
return fatal
}, "100ms").Should(BeTrue())
Eventually(errChan).Should(Receive())
})
})
})
Describe("Server stop", func() {
When("Stop is called", func() {
It("stop was called 2 times, start should fail", func() {
defer func() { Log().ExitFunc = nil }()
var fatal bool
Log().ExitFunc = func(int) { fatal = true }
// create server
server, err := NewServer(&config.Config{
Upstream: config.UpstreamConfig{
@ -609,24 +599,24 @@ var _ = Describe("Running DNS server", func() {
Expect(err).Should(Succeed())
errChan := make(chan error, 10)
// start server
go func() {
server.Start()
server.Start(errChan)
}()
defer server.Stop()
time.Sleep(100 * time.Millisecond)
server.Stop()
err = server.Stop()
// stop server, should be ok
Expect(fatal).Should(BeFalse())
Expect(err).Should(Succeed())
// stop again, should raise fatal error
server.Stop()
// stop again, should raise error
err = server.Stop()
Expect(fatal).Should(BeTrue())
Expect(err).Should(HaveOccurred())
})
})
})