blocky/resolver/conditional_upstream_resolv...

216 lines
6.3 KiB
Go
Raw Normal View History

2020-01-12 18:23:35 +01:00
package resolver
import (
"context"
2021-08-25 22:06:34 +02:00
"github.com/0xERR0R/blocky/config"
. "github.com/0xERR0R/blocky/helpertest"
refactor: configuration rework (usage and printing) (#920) * refactor: make `config.Duration` a struct with `time.Duration` embed Allows directly calling `time.Duration` methods. * refactor(HostsFileResolver): don't copy individual config items The idea is to make adding configuration options easier, and searching for references straight forward. * refactor: move config printing to struct and use a logger Using a logger allows using multiple levels so the whole configuration can be printed in trace/verbose mode, but only important parts are shown by default. * squash: rename `Cast` to `ToDuration` * squash: revert `Duration` to a simple wrapper ("new type" pattern) * squash: `Duration.IsZero` tests * squash: refactor resolvers to rely on their config directly if possible * squash: implement `IsEnabled` and `LogValues` for all resolvers * refactor: use go-enum `--values` to simplify getting all log fields * refactor: simplify `QType` unmarshaling * squash: rename `ValueLogger` to `Configurable` * squash: rename `UpstreamConfig` to `ParallelBestConfig` * squash: rename `RewriteConfig` to `RewriterConfig` * squash: config tests * squash: resolver tests * squash: add `ForEach` test and improve `Chain` ones * squash: simplify implementing `config.Configurable` * squash: minor changes for better coverage * squash: more `UnmarshalYAML` -> `UnmarshalText` * refactor: move `config.Upstream` into own file * refactor: add `Resolver.Type` method * squash: add `log` method to `typed` to use `Resolover.Type` as prefix * squash: tweak startup config logging * squash: add `LogResolverConfig` tests * squash: make sure all options of type `Duration` use `%s`
2023-03-12 22:14:10 +01:00
"github.com/0xERR0R/blocky/log"
. "github.com/0xERR0R/blocky/model"
2021-08-25 22:06:34 +02:00
"github.com/0xERR0R/blocky/util"
2020-01-12 18:23:35 +01:00
"github.com/miekg/dns"
2022-03-03 11:27:27 +01:00
. "github.com/onsi/ginkgo/v2"
2020-05-04 22:20:13 +02:00
. "github.com/onsi/gomega"
2020-01-12 18:23:35 +01:00
"github.com/stretchr/testify/mock"
)
var _ = Describe("ConditionalUpstreamResolver", Label("conditionalResolver"), func() {
2020-05-04 22:20:13 +02:00
var (
sut *ConditionalUpstreamResolver
sutConfig config.ConditionalUpstream
m *mockResolver
ctx context.Context
cancelFn context.CancelFunc
2020-05-04 22:20:13 +02:00
)
2020-01-15 22:11:02 +01:00
refactor: configuration rework (usage and printing) (#920) * refactor: make `config.Duration` a struct with `time.Duration` embed Allows directly calling `time.Duration` methods. * refactor(HostsFileResolver): don't copy individual config items The idea is to make adding configuration options easier, and searching for references straight forward. * refactor: move config printing to struct and use a logger Using a logger allows using multiple levels so the whole configuration can be printed in trace/verbose mode, but only important parts are shown by default. * squash: rename `Cast` to `ToDuration` * squash: revert `Duration` to a simple wrapper ("new type" pattern) * squash: `Duration.IsZero` tests * squash: refactor resolvers to rely on their config directly if possible * squash: implement `IsEnabled` and `LogValues` for all resolvers * refactor: use go-enum `--values` to simplify getting all log fields * refactor: simplify `QType` unmarshaling * squash: rename `ValueLogger` to `Configurable` * squash: rename `UpstreamConfig` to `ParallelBestConfig` * squash: rename `RewriteConfig` to `RewriterConfig` * squash: config tests * squash: resolver tests * squash: add `ForEach` test and improve `Chain` ones * squash: simplify implementing `config.Configurable` * squash: minor changes for better coverage * squash: more `UnmarshalYAML` -> `UnmarshalText` * refactor: move `config.Upstream` into own file * refactor: add `Resolver.Type` method * squash: add `log` method to `typed` to use `Resolover.Type` as prefix * squash: tweak startup config logging * squash: add `LogResolverConfig` tests * squash: make sure all options of type `Duration` use `%s`
2023-03-12 22:14:10 +01:00
Describe("Type", func() {
It("follows conventions", func() {
expectValidResolverType(sut)
})
})
2020-05-04 22:20:13 +02:00
BeforeEach(func() {
ctx, cancelFn = context.WithCancel(context.Background())
DeferCleanup(cancelFn)
fbTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, A, "123.124.122.122")
return response
})
otherTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 250, A, "192.192.192.192")
return response
})
dotTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 223, A, "168.168.168.168")
return response
})
refuseTestUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
response = new(dns.Msg)
response.Rcode = dns.RcodeRefused
// question section in response should be empty
request.Question = make([]dns.Question, 0)
return response
})
sutConfig = config.ConditionalUpstream{
Mapping: config.ConditionalUpstreamMapping{
Upstreams: map[string][]config.Upstream{
"fritz.box": {fbTestUpstream.Start()},
"other.box": {otherTestUpstream.Start()},
"refused.domain": {refuseTestUpstream.Start()},
".": {dotTestUpstream.Start()},
},
},
}
})
JustBeforeEach(func() {
sut, _ = NewConditionalUpstreamResolver(ctx, sutConfig, defaultUpstreamsConfig, systemResolverBootstrap)
m = &mockResolver{}
2020-05-04 22:20:13 +02:00
m.On("Resolve", mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
sut.Next(m)
})
2020-01-15 22:11:02 +01:00
refactor: configuration rework (usage and printing) (#920) * refactor: make `config.Duration` a struct with `time.Duration` embed Allows directly calling `time.Duration` methods. * refactor(HostsFileResolver): don't copy individual config items The idea is to make adding configuration options easier, and searching for references straight forward. * refactor: move config printing to struct and use a logger Using a logger allows using multiple levels so the whole configuration can be printed in trace/verbose mode, but only important parts are shown by default. * squash: rename `Cast` to `ToDuration` * squash: revert `Duration` to a simple wrapper ("new type" pattern) * squash: `Duration.IsZero` tests * squash: refactor resolvers to rely on their config directly if possible * squash: implement `IsEnabled` and `LogValues` for all resolvers * refactor: use go-enum `--values` to simplify getting all log fields * refactor: simplify `QType` unmarshaling * squash: rename `ValueLogger` to `Configurable` * squash: rename `UpstreamConfig` to `ParallelBestConfig` * squash: rename `RewriteConfig` to `RewriterConfig` * squash: config tests * squash: resolver tests * squash: add `ForEach` test and improve `Chain` ones * squash: simplify implementing `config.Configurable` * squash: minor changes for better coverage * squash: more `UnmarshalYAML` -> `UnmarshalText` * refactor: move `config.Upstream` into own file * refactor: add `Resolver.Type` method * squash: add `log` method to `typed` to use `Resolover.Type` as prefix * squash: tweak startup config logging * squash: add `LogResolverConfig` tests * squash: make sure all options of type `Duration` use `%s`
2023-03-12 22:14:10 +01:00
Describe("IsEnabled", func() {
It("is true", func() {
Expect(sut.IsEnabled()).Should(BeTrue())
})
})
Describe("LogConfig", func() {
It("should log something", func() {
logger, hook := log.NewMockEntry()
sut.LogConfig(logger)
Expect(hook.Calls).ShouldNot(BeEmpty())
})
})
2020-05-04 22:20:13 +02:00
Describe("Resolve conditional DNS queries via defined DNS server", func() {
When("conditional resolver returns error code", func() {
It("Should be returned without changes", func() {
Expect(sut.Resolve(ctx, newRequest("refused.domain.", A))).
Should(
SatisfyAll(
HaveNoAnswer(),
HaveResponseType(ResponseTypeCONDITIONAL),
HaveReason("CONDITIONAL"),
HaveReturnCode(dns.RcodeRefused),
))
// no call to next resolver
Expect(m.Calls).Should(BeEmpty())
})
})
2020-05-04 22:20:13 +02:00
When("Query is exact equal defined condition in mapping", func() {
Context("first mapping entry", func() {
It("Should resolve the IP of conditional DNS", func() {
Expect(sut.Resolve(ctx, newRequest("fritz.box.", A))).
Should(
SatisfyAll(
BeDNSRecord("fritz.box.", A, "123.124.122.122"),
HaveTTL(BeNumerically("==", 123)),
HaveResponseType(ResponseTypeCONDITIONAL),
HaveReason("CONDITIONAL"),
HaveReturnCode(dns.RcodeSuccess),
))
2020-05-04 22:20:13 +02:00
// no call to next resolver
Expect(m.Calls).Should(BeEmpty())
})
})
Context("last mapping entry", func() {
It("Should resolve the IP of conditional DNS", func() {
Expect(sut.Resolve(ctx, newRequest("other.box.", A))).
Should(
SatisfyAll(
BeDNSRecord("other.box.", A, "192.192.192.192"),
HaveTTL(BeNumerically("==", 250)),
HaveResponseType(ResponseTypeCONDITIONAL),
HaveReason("CONDITIONAL"),
HaveReturnCode(dns.RcodeSuccess),
))
2020-05-04 22:20:13 +02:00
// no call to next resolver
Expect(m.Calls).Should(BeEmpty())
})
})
})
When("Query is a subdomain of defined condition in mapping", func() {
It("Should resolve the IP of subdomain", func() {
Expect(sut.Resolve(ctx, newRequest("test.fritz.box.", A))).
Should(
SatisfyAll(
BeDNSRecord("test.fritz.box.", A, "123.124.122.122"),
HaveTTL(BeNumerically("==", 123)),
HaveResponseType(ResponseTypeCONDITIONAL),
HaveReason("CONDITIONAL"),
HaveReturnCode(dns.RcodeSuccess),
))
2020-05-04 22:20:13 +02:00
// no call to next resolver
Expect(m.Calls).Should(BeEmpty())
})
})
When("Query is not fqdn and . condition is defined in mapping", func() {
It("Should resolve the IP of .", func() {
Expect(sut.Resolve(ctx, newRequest("test.", A))).
Should(
SatisfyAll(
BeDNSRecord("test.", A, "168.168.168.168"),
HaveTTL(BeNumerically("==", 223)),
HaveResponseType(ResponseTypeCONDITIONAL),
HaveReason("CONDITIONAL"),
HaveReturnCode(dns.RcodeSuccess),
))
// no call to next resolver
Expect(m.Calls).Should(BeEmpty())
})
})
2020-05-04 22:20:13 +02:00
})
Describe("Delegation to next resolver", func() {
When("Query doesn't match defined mapping", func() {
It("should delegate to next resolver", func() {
Expect(sut.Resolve(ctx, newRequest("google.com.", A))).
Should(
SatisfyAll(
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
2020-05-04 22:20:13 +02:00
m.AssertExpectations(GinkgoT())
})
})
})
2020-01-15 22:11:02 +01:00
When("upstream is invalid", func() {
It("errors during construction", func() {
b := newTestBootstrap(ctx, &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}})
upstreamsCfg := defaultUpstreamsConfig
upstreamsCfg.Init.Strategy = config.InitStrategyFailOnError
sutConfig := config.ConditionalUpstream{
Mapping: config.ConditionalUpstreamMapping{
Upstreams: map[string][]config.Upstream{
".": {config.Upstream{Host: "example.com"}},
},
},
}
r, err := NewConditionalUpstreamResolver(ctx, sutConfig, upstreamsCfg, b)
Expect(err).Should(HaveOccurred())
Expect(r).Should(BeNil())
})
})
2020-05-04 22:20:13 +02:00
})