refactor(resolvers): make `Bootstrap` implement `Resolver`

This commit is contained in:
ThinkChaos 2023-11-20 16:22:29 -05:00
parent 11543356b6
commit 659076dd7b
6 changed files with 109 additions and 22 deletions

View File

@ -271,6 +271,16 @@ type (
bootstrapDNSConfig []BootstrappedUpstreamConfig
)
func (b *BootstrapDNSConfig) IsEnabled() bool {
return len(*b) != 0
}
func (b *BootstrapDNSConfig) LogConfig(*logrus.Entry) {
// This should not be called, at least for now:
// The Boostrap resolver is not in the chain and thus its config is not logged
panic("not implemented")
}
// split in two types to avoid infinite recursion. See `BootstrappedUpstreamConfig.UnmarshalYAML`.
type (
BootstrappedUpstreamConfig bootstrappedUpstreamConfig

View File

@ -681,6 +681,31 @@ bootstrapDns:
})
})
Describe("BootstrapDNSConfig", func() {
It("is not enabled when empty", func() {
var sut BootstrapDNSConfig
Expect(sut.IsEnabled()).Should(BeFalse())
})
It("is enabled if non empty", func() {
sut := BootstrapDNSConfig{
BootstrappedUpstreamConfig{},
BootstrappedUpstreamConfig{},
}
Expect(sut.IsEnabled()).Should(BeTrue())
})
It("LogConfig panics", func() {
sut := BootstrapDNSConfig{}
Expect(func() {
sut.LogConfig(logger)
}).Should(Panic())
})
})
Describe("SourceLoadingConfig", func() {
var (
ctx context.Context

View File

@ -26,7 +26,8 @@ const (
// Bootstrap allows resolving hostnames using the configured bootstrap DNS.
type Bootstrap struct {
log *logrus.Entry
configurable[*config.BootstrapDNSConfig]
typed
resolver Resolver
bootstraped bootstrapedResolvers
@ -44,8 +45,6 @@ type Bootstrap struct {
// NewBootstrap creates and returns a new Bootstrap.
// Internally, it uses a CachingResolver and an UpstreamResolver.
func NewBootstrap(ctx context.Context, cfg *config.Config) (b *Bootstrap, err error) {
logger := log.PrefixedLog("bootstrap")
timeout := defaultTimeout
if cfg.Upstreams.Timeout.IsAboveZero() {
timeout = cfg.Upstreams.Timeout.ToDuration()
@ -55,7 +54,9 @@ func NewBootstrap(ctx context.Context, cfg *config.Config) (b *Bootstrap, err er
// This also prevents the GC to clean up these two structs, but is not currently an
// issue since they stay allocated until the process terminates
b = &Bootstrap{
log: logger,
configurable: withConfig(&cfg.BootstrapDNS),
typed: withType("bootstrap"),
connectIPVersion: cfg.ConnectIPVersion,
systemResolver: net.DefaultResolver,
@ -71,7 +72,7 @@ func NewBootstrap(ctx context.Context, cfg *config.Config) (b *Bootstrap, err er
}
if len(bootstraped) == 0 {
logger.Infof("bootstrapDns is not configured, will use system resolver")
b.log().Info("bootstrapDns is not configured, will use system resolver")
return b, nil
}
@ -103,6 +104,20 @@ func NewBootstrap(ctx context.Context, cfg *config.Config) (b *Bootstrap, err er
return b, nil
}
func (b *Bootstrap) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
if b.resolver == nil {
// We could implement most queries using the `b.systemResolver.Lookup*` functions,
// but that requires a lot of boilerplate to translate from `dns` to `net` and back.
return nil, errors.New("cannot resolve arbitrary requests using the system resolver")
}
// Add bootstrap prefix to all inner resolver logs
req := *request
req.Log = log.WithPrefix(req.Log, b.Type())
return b.resolver.Resolve(ctx, &req)
}
func (b *Bootstrap) UpstreamIPs(ctx context.Context, r *UpstreamResolver) (*IPSet, error) {
hostname := r.cfg.Host
@ -112,7 +127,7 @@ func (b *Bootstrap) UpstreamIPs(ctx context.Context, r *UpstreamResolver) (*IPSe
ips, err := b.resolveUpstream(ctx, r, hostname)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not resolve IPs for upstream %s: %w", hostname, err)
}
return newIPSet(ips), nil
@ -149,7 +164,7 @@ func (b *Bootstrap) NewHTTPTransport() *http.Transport {
}
func (b *Bootstrap) dialContext(ctx context.Context, network, addr string) (net.Conn, error) {
logger := b.log.WithField("network", network).WithField("addr", addr)
logger := b.log().WithFields(logrus.Fields{"network": network, "addr": addr})
host, port, err := net.SplitHostPort(addr)
if err != nil {
@ -217,7 +232,7 @@ func (b *Bootstrap) resolveType(ctx context.Context, hostname string, qType dns.
req := model.Request{
Req: util.NewMsgWithQuestion(hostname, qType),
Log: b.log,
Log: b.log(),
}
rsp, err := b.resolver.Resolve(ctx, &req)
@ -243,7 +258,7 @@ func (b *Bootstrap) resolveType(ctx context.Context, hostname string, qType dns.
return ips, nil
}
// map of bootstraped resolvers their hardcoded IPs
// map of bootstraped resolvers to their hardcoded IPs
type bootstrapedResolvers map[Resolver][]net.IP
func newBootstrapedResolvers(
@ -267,7 +282,7 @@ func newBootstrapedResolvers(
continue
}
var ips []net.IP
ips := make([]net.IP, 0, len(upstreamCfg.IPs)+1)
if ip := net.ParseIP(upstream.Host); ip != nil {
ips = append(ips, ip)

View File

@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"sync/atomic"
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/log"
@ -24,7 +25,7 @@ import (
var _ = Describe("Bootstrap", Label("bootstrap"), func() {
var (
sut *Bootstrap
sutConfig *config.Config
sutConfig config.Config
ctx context.Context
cancelFn context.CancelFunc
@ -32,7 +33,7 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
)
BeforeEach(func() {
sutConfig = &config.Config{
sutConfig = config.Config{
BootstrapDNS: []config.BootstrappedUpstreamConfig{
{
Upstream: config.Upstream{
@ -50,23 +51,23 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
})
JustBeforeEach(func() {
sut, err = NewBootstrap(ctx, sutConfig)
sut, err = NewBootstrap(ctx, &sutConfig)
Expect(err).Should(Succeed())
})
Describe("configuration", func() {
When("is not specified", func() {
BeforeEach(func() {
sutConfig = &config.Config{}
sutConfig.BootstrapDNS = config.BootstrapDNSConfig{}
})
It("should use the system resolver", func() {
usedSystemResolver := make(chan bool, 100)
var usedSystemResolver atomic.Bool
sut.systemResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
usedSystemResolver <- true
usedSystemResolver.Store(true)
return nil, errors.New("don't actually do anything")
},
@ -74,7 +75,7 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
_, err := sut.resolveUpstream(ctx, nil, "example.com")
Expect(err).Should(HaveOccurred())
Expect(usedSystemResolver).Should(Receive(BeTrue()))
Expect(usedSystemResolver.Load()).Should(BeTrue())
})
Describe("HTTP transport", func() {
@ -113,7 +114,7 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
Context("using TCP UDP", func() {
When("hostname is an IP", func() {
BeforeEach(func() {
sutConfig = &config.Config{
sutConfig = config.Config{
BootstrapDNS: []config.BootstrappedUpstreamConfig{
{
Upstream: config.Upstream{
@ -154,7 +155,7 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
When("extra IPs are configured", func() {
BeforeEach(func() {
sutConfig = &config.Config{
sutConfig = config.Config{
BootstrapDNS: []config.BootstrappedUpstreamConfig{
{
Upstream: config.Upstream{
@ -334,6 +335,29 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
Expect(rsp.Res.Question[0].Name).Should(Equal("example.com."))
Expect(rsp.Res.Id).ShouldNot(Equal(bootstrapResponse.Id))
})
Describe("Resolve", func() {
It("calls the usptream resolver", func(ctx context.Context) {
expected := new(model.Response)
bootstrapUpstream.On("Resolve", mock.Anything).Return(expected, nil)
Expect(sut.Resolve(ctx, newRequest("example.com.", A))).
Should(BeIdenticalTo(expected))
})
When("using the system resolver", func() {
JustBeforeEach(func() {
sut = systemResolverBootstrap
})
It("can't resolve arbitrary requests", func(ctx context.Context) {
_, err := sut.Resolve(ctx, newRequest("example.com.", A))
Expect(err).
Should(MatchError(errArbitrarySystemResolverRequest))
})
})
})
})
Describe("HTTP Transport", func() {

View File

@ -12,6 +12,7 @@ import (
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/util"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/0xERR0R/blocky/model"
@ -119,13 +120,22 @@ func autoAnswer(qType dns.Type, qName string) (*dns.Msg, error) {
// newTestBootstrap creates a test Bootstrap
func newTestBootstrap(ctx context.Context, response *dns.Msg) *Bootstrap {
const cfgTxt = `
upstream: https://mock
ips:
- 0.0.0.0
`
bootstrapUpstream := &mockResolver{}
b, err := NewBootstrap(ctx, &config.Config{})
var bCfg config.BootstrapDNSConfig
err := yaml.UnmarshalStrict([]byte(cfgTxt), &bCfg)
util.FatalOnError("test bootstrap config is broken, did you change the struct?", err)
b, err := NewBootstrap(ctx, &config.Config{BootstrapDNS: bCfg})
util.FatalOnError("can't create bootstrap", err)
b.resolver = bootstrapUpstream
b.bootstraped = bootstrapedResolvers{bootstrapUpstream: []net.IP{}}
if response != nil {
bootstrapUpstream.

View File

@ -12,7 +12,10 @@ import (
. "github.com/onsi/gomega"
)
var systemResolverBootstrap = &Bootstrap{dialer: newMockDialer()}
var systemResolverBootstrap = &Bootstrap{
dialer: newMockDialer(),
configurable: withConfig(newBootstrapConfig(&config.Config{Upstreams: defaultUpstreamsConfig})),
}
var _ = Describe("Resolver", func() {
Describe("Chains", func() {