blocky/resolver/resolver.go

217 lines
5.0 KiB
Go
Raw Normal View History

2020-01-12 18:23:35 +01:00
package resolver
import (
"fmt"
2020-01-12 18:23:35 +01:00
"net"
"time"
2020-01-12 18:23:35 +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
"github.com/0xERR0R/blocky/config"
2021-08-25 22:06:34 +02:00
"github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/model"
2021-08-25 22:06:34 +02:00
"github.com/0xERR0R/blocky/util"
"github.com/miekg/dns"
2021-08-25 22:06:34 +02:00
2020-01-12 18:23:35 +01:00
"github.com/sirupsen/logrus"
)
func newRequest(question string, rType dns.Type, logger ...*logrus.Entry) *model.Request {
2021-01-16 22:24:05 +01:00
var loggerEntry *logrus.Entry
if len(logger) == 1 {
loggerEntry = logger[0]
} else {
loggerEntry = logrus.NewEntry(log.Log())
2021-01-16 22:24:05 +01:00
}
return &model.Request{
Req: util.NewMsgWithQuestion(question, rType),
2021-01-16 22:24:05 +01:00
Log: loggerEntry,
2021-09-09 22:57:05 +02:00
Protocol: model.RequestProtocolUDP,
2020-05-04 22:20:13 +02:00
}
}
func newRequestWithClient(question string, rType dns.Type, ip string, clientNames ...string) *model.Request {
return &model.Request{
2020-05-04 22:20:13 +02:00
ClientIP: net.ParseIP(ip),
ClientNames: clientNames,
Req: util.NewMsgWithQuestion(question, rType),
Log: logrus.NewEntry(log.Log()),
2020-05-04 22:20:13 +02:00
RequestTS: time.Time{},
2021-09-09 22:57:05 +02:00
Protocol: model.RequestProtocolUDP,
2020-05-04 22:20:13 +02:00
}
}
// newResponse creates a response to the given request
func newResponse(request *model.Request, rcode int, rtype model.ResponseType, reason string) *model.Response {
response := new(dns.Msg)
response.SetReply(request.Req)
response.Rcode = rcode
return &model.Response{
Res: response,
RType: rtype,
Reason: reason,
}
}
func newRequestWithClientID(question string, rType dns.Type, ip, requestClientID string) *model.Request {
return &model.Request{
ClientIP: net.ParseIP(ip),
RequestClientID: requestClientID,
Req: util.NewMsgWithQuestion(question, rType),
Log: logrus.NewEntry(log.Log()),
RequestTS: time.Time{},
Protocol: model.RequestProtocolUDP,
}
}
// Resolver generic interface for all resolvers
2020-01-12 18:23:35 +01:00
type Resolver interface {
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
config.Configurable
// Type returns a short, user-friendly, name for the resolver.
//
// It should be the same for all instances of a specific Resolver type.
Type() string
// Resolve performs resolution of a DNS request
Resolve(req *model.Request) (*model.Response, error)
2020-01-12 18:23:35 +01:00
}
// ChainedResolver represents a resolver, which can delegate result to the next one
2020-01-12 18:23:35 +01:00
type ChainedResolver interface {
Resolver
2021-02-26 21:39:41 +01:00
2021-02-26 21:44:53 +01:00
// Next sets the next resolver
2020-01-12 18:23:35 +01:00
Next(n Resolver)
2021-02-26 21:39:41 +01:00
// GetNext returns the next resolver
2020-01-12 18:23:35 +01:00
GetNext() Resolver
}
// NextResolver is the base implementation of ChainedResolver
2020-01-12 18:23:35 +01:00
type NextResolver struct {
next Resolver
}
2021-02-26 21:44:53 +01:00
// Next sets the next resolver
2020-01-12 18:23:35 +01:00
func (r *NextResolver) Next(n Resolver) {
r.next = n
}
2021-02-26 21:44:53 +01:00
// GetNext returns the next resolver
2020-01-12 18:23:35 +01:00
func (r *NextResolver) GetNext() Resolver {
return r.next
}
// NamedResolver is a resolver with a special name
type NamedResolver interface {
// Name returns the full name of the resolver
Name() string
}
// Chain creates a chain of resolvers
func Chain(resolvers ...Resolver) ChainedResolver {
for i, res := range resolvers {
if i+1 < len(resolvers) {
if cr, ok := res.(ChainedResolver); ok {
cr.Next(resolvers[i+1])
}
}
}
return resolvers[0].(ChainedResolver)
}
func GetFromChainWithType[T any](resolver ChainedResolver) (result T, err error) {
for resolver != nil {
if result, found := resolver.(T); found {
return result, nil
}
if cr, ok := resolver.GetNext().(ChainedResolver); ok {
resolver = cr
} else {
break
}
}
return result, fmt.Errorf("type was not found in the chain")
}
2020-02-23 22:32:24 +01:00
// Name returns a user-friendly name of a resolver
2020-02-23 22:32:24 +01:00
func Name(resolver Resolver) string {
if named, ok := resolver.(NamedResolver); ok {
return named.Name()
}
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
return resolver.Type()
}
// ForEach iterates over all resolvers in the chain.
//
// If resolver is not a chain, or is unlinked,
// the callback is called exactly once.
func ForEach(resolver Resolver, callback func(Resolver)) {
for resolver != nil {
callback(resolver)
if chained, ok := resolver.(ChainedResolver); ok {
resolver = chained.GetNext()
} else {
break
}
}
}
// LogResolverConfig logs the resolver's type and config.
func LogResolverConfig(res Resolver, logger *logrus.Entry) {
// Use the type, not the full typeName, to avoid redundant information with the config
typeName := res.Type()
if !res.IsEnabled() {
logger.Debugf("-> %s: disabled", typeName)
return
}
logger.Infof("-> %s:", typeName)
log.WithIndent(logger, " ", res.LogConfig)
}
// Should be embedded in a Resolver to auto-implement `Resolver.Type`.
type typed struct {
typeName string
}
func withType(t string) typed {
return typed{typeName: t}
}
// Type implements `Resolver`.
func (t *typed) Type() string {
return t.typeName
}
func (t *typed) log() *logrus.Entry {
return log.PrefixedLog(t.Type())
}
// Should be embedded in a Resolver to auto-implement `config.Configurable`.
type configurable[T config.Configurable] struct {
cfg T
}
func withConfig[T config.Configurable](cfg T) configurable[T] {
return configurable[T]{cfg: cfg}
}
// IsEnabled implements `config.Configurable`.
func (c *configurable[T]) IsEnabled() bool {
return c.cfg.IsEnabled()
}
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
// LogConfig implements `config.Configurable`.
func (c *configurable[T]) LogConfig(logger *logrus.Entry) {
c.cfg.LogConfig(logger)
2020-02-23 22:32:24 +01:00
}