blocky/resolver/upstream_tree_resolver_test.go

290 lines
8.1 KiB
Go

package resolver
import (
"context"
"fmt"
"github.com/0xERR0R/blocky/config"
. "github.com/0xERR0R/blocky/helpertest"
"github.com/0xERR0R/blocky/log"
. "github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("UpstreamTreeResolver", Label("upstreamTreeResolver"), func() {
var (
sut Resolver
sutConfig config.Upstreams
err error
ctx context.Context
cancelFn context.CancelFunc
)
BeforeEach(func() {
ctx, cancelFn = context.WithCancel(context.Background())
DeferCleanup(cancelFn)
sutConfig = defaultUpstreamsConfig
})
JustBeforeEach(func() {
sut, err = NewUpstreamTreeResolver(ctx, sutConfig, systemResolverBootstrap)
})
When("it has no configuration", func() {
BeforeEach(func() {
sutConfig = config.Upstreams{}
})
It("should return error", func() {
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("no external DNS resolvers configured")))
Expect(sut).To(BeNil())
})
})
When("it has only default group", func() {
BeforeEach(func() {
sutConfig.Groups = config.UpstreamGroups{
upstreamDefaultCfgName: {
{Host: "wrong"},
{Host: "127.0.0.1"},
},
}
})
When("strategy is parallel", func() {
BeforeEach(func() {
sutConfig.Strategy = config.UpstreamStrategyParallelBest
})
It("returns the resolver directly", func() {
Expect(err).ToNot(HaveOccurred())
_, ok := sut.(*ParallelBestResolver)
Expect(ok).Should(BeTrue())
})
})
When("strategy is strict", func() {
BeforeEach(func() {
sutConfig.Strategy = config.UpstreamStrategyStrict
})
It("returns the resolver directly", func() {
Expect(err).ToNot(HaveOccurred())
_, ok := sut.(*StrictResolver)
Expect(ok).Should(BeTrue())
})
})
})
When("it has multiple groups", func() {
BeforeEach(func() {
sutConfig.Groups = config.UpstreamGroups{
upstreamDefaultCfgName: {
{Host: "wrong"},
{Host: "127.0.0.1"},
},
"test": {
{Host: "some-resolver"},
},
}
})
Describe("Type", func() {
It("does not return error", func() {
Expect(err).ToNot(HaveOccurred())
})
It("follows conventions", func() {
expectValidResolverType(sut)
})
It("returns upstream_tree", func() {
Expect(sut.Type()).To(Equal(upstreamTreeResolverType))
})
})
Describe("Configuration output", func() {
It("should return configuration", func() {
Expect(sut.IsEnabled()).Should(BeTrue())
logger, hook := log.NewMockEntry()
sut.LogConfig(logger)
Expect(hook.Calls).ToNot(BeEmpty())
})
})
Describe("Name", func() {
var utrSut *UpstreamTreeResolver
JustBeforeEach(func() {
utrSut = sut.(*UpstreamTreeResolver)
})
It("should contain correct resolver", func() {
name := utrSut.Name()
Expect(name).ShouldNot(BeEmpty())
Expect(name).Should(ContainSubstring(upstreamTreeResolverType))
})
})
When("init strategy is failOnError", func() {
BeforeEach(func() {
sutConfig.Init.Strategy = config.InitStrategyFailOnError
})
It("should fail", func() {
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("no valid upstream")))
Expect(sut).To(BeNil())
})
})
When("client specific resolvers are defined", func() {
groups := map[string]string{
upstreamDefaultCfgName: "127.0.0.1",
"laptop": "127.0.0.2",
"client-*-m": "127.0.0.3",
"client[0-9]": "127.0.0.4",
"192.168.178.33": "127.0.0.5",
"10.43.8.67/28": "127.0.0.6",
"name-matches1": "127.0.0.7",
"name-matches*": "127.0.0.8",
}
BeforeEach(func() {
sutConfig.Groups = make(config.UpstreamGroups, len(groups))
for group, ip := range groups {
Expect(ip).ShouldNot(BeNil())
server := NewMockUDPUpstreamServer().WithAnswerRR(fmt.Sprintf("example.com 123 IN A %s", ip))
sutConfig.Groups[group] = []config.Upstream{server.Start()}
}
})
It("Should use default if client name or IP don't match", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.55", "test")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["default"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client name matches exact", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.55", "laptop")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["laptop"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client name matches with wildcard", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.55", "client-test-m")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["client-*-m"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client name matches with range wildcard", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.55", "client7")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["client[0-9]"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client IP matches", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.33", "noname")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["192.168.178.33"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client name (containing IP) matches", func() {
request := newRequestWithClient("example.com.", A, "0.0.0.0", "192.168.178.33")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["192.168.178.33"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client specific resolver if client's CIDR (10.43.8.64 - 10.43.8.79) matches", func() {
request := newRequestWithClient("example.com.", A, "10.43.8.70", "noname")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["10.43.8.67/28"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use exact IP match before client name match", func() {
request := newRequestWithClient("example.com.", A, "192.168.178.33", "laptop")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["192.168.178.33"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use client name match before CIDR match", func() {
request := newRequestWithClient("example.com.", A, "10.43.8.70", "laptop")
Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
BeDNSRecord("example.com.", A, groups["laptop"]),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
})
It("Should use one of the matching resolvers & log warning", func() {
logger, hook := log.NewMockEntry()
ctx, _ = log.NewCtx(ctx, logger)
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "0.0.0.0", "name-matches1"))).
Should(
SatisfyAll(
SatisfyAny(
BeDNSRecord("example.com.", A, groups["name-matches1"]),
BeDNSRecord("example.com.", A, groups["name-matches*"]),
),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
))
Expect(hook.Messages).Should(ContainElement(ContainSubstring("client matches multiple groups")))
})
})
})
})