blocky/resolver/client_names_resolver.go

174 lines
4.2 KiB
Go
Raw Normal View History

2020-01-12 18:23:35 +01:00
package resolver
import (
"context"
2020-01-12 18:23:35 +01:00
"net"
"strings"
"time"
"github.com/0xERR0R/blocky/cache/expirationcache"
2021-08-25 22:06:34 +02:00
"github.com/0xERR0R/blocky/config"
"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"
"github.com/sirupsen/logrus"
)
// ClientNamesResolver tries to determine client name by asking responsible DNS server via rDNS (reverse lookup)
2020-01-12 18:23:35 +01:00
type ClientNamesResolver struct {
2023-11-21 23:19:19 +01:00
configurable[*config.ClientLookup]
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
NextResolver
typed
cache expirationcache.ExpiringCache[[]string]
2020-01-12 18:23:35 +01:00
externalResolver Resolver
}
// NewClientNamesResolver creates new resolver instance
func NewClientNamesResolver(ctx context.Context,
cfg config.ClientLookup, upstreamsCfg config.Upstreams, bootstrap *Bootstrap,
) (cr *ClientNamesResolver, err error) {
2020-01-12 18:23:35 +01:00
var r Resolver
if !cfg.Upstream.IsDefault() {
r, err = NewUpstreamResolver(ctx, newUpstreamConfig(cfg.Upstream, upstreamsCfg), bootstrap)
if err != nil {
return nil, err
}
2020-01-12 18:23:35 +01:00
}
cr = &ClientNamesResolver{
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
configurable: withConfig(&cfg),
typed: withType("client_names"),
cache: expirationcache.NewCache[[]string](ctx, expirationcache.Options{
CleanupInterval: time.Hour,
}),
2020-01-12 18:23:35 +01:00
externalResolver: r,
}
return
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
// LogConfig implements `config.Configurable`.
func (r *ClientNamesResolver) LogConfig(logger *logrus.Entry) {
r.cfg.LogConfig(logger)
2020-05-17 21:50:40 +02: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
logger.Infof("cache entries = %d", r.cache.TotalCount())
2020-01-12 18:23:35 +01:00
}
// Resolve tries to resolve the client name from the ip address
func (r *ClientNamesResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
clientNames := r.getClientNames(ctx, request)
2020-01-12 18:23:35 +01:00
request.ClientNames = clientNames
ctx, _ = log.CtxWithFields(ctx, logrus.Fields{"client_names": strings.Join(clientNames, "; ")})
2020-01-12 18:23:35 +01:00
return r.next.Resolve(ctx, request)
2020-01-12 18:23:35 +01:00
}
// returns names of client
func (r *ClientNamesResolver) getClientNames(ctx context.Context, request *model.Request) []string {
if request.RequestClientID != "" {
return []string{request.RequestClientID}
}
2020-01-12 18:23:35 +01:00
ip := request.ClientIP
if ip == nil {
return []string{}
}
2020-07-08 21:56:09 +02:00
c, _ := r.cache.Get(ip.String())
if c != nil {
// return copy here, since we can't control all usages here
cpy := make([]string, len(*c))
copy(cpy, *c)
return cpy
2020-01-12 18:23:35 +01:00
}
names := r.resolveClientNames(ctx, ip)
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
r.cache.Put(ip.String(), &names, time.Hour)
2020-01-12 18:23:35 +01:00
return names
}
func extractClientNamesFromAnswer(answer []dns.RR, fallbackIP net.IP) (clientNames []string) {
for _, answer := range answer {
if t, ok := answer.(*dns.PTR); ok {
hostName := strings.TrimSuffix(t.Ptr, ".")
clientNames = append(clientNames, hostName)
}
}
if len(clientNames) == 0 {
clientNames = []string{fallbackIP.String()}
}
return
}
2020-05-17 21:50:40 +02:00
// tries to resolve client name from mapping, performs reverse DNS lookup otherwise
func (r *ClientNamesResolver) resolveClientNames(ctx context.Context, ip net.IP) (result []string) {
ctx, logger := r.log(ctx)
2020-05-17 21:50:40 +02:00
// try client mapping first
result = r.getNameFromIPMapping(ip, result)
if len(result) > 0 {
return result
2020-05-17 21:50:40 +02:00
}
if r.externalResolver == nil {
return []string{ip.String()}
}
reverse, _ := dns.ReverseAddr(ip.String())
2020-01-12 18:23:35 +01:00
resp, err := r.externalResolver.Resolve(ctx, &model.Request{
Req: util.NewMsgWithQuestion(reverse, dns.Type(dns.TypePTR)),
})
if err != nil {
logger.Error("can't resolve client name: ", err)
2020-01-12 18:23:35 +01:00
return []string{ip.String()}
}
2020-01-12 18:23:35 +01:00
clientNames := extractClientNamesFromAnswer(resp.Res.Answer, ip)
2020-01-12 18:23:35 +01:00
// optional: if singleNameOrder is set, use only one name in the defined order
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
if len(r.cfg.SingleNameOrder) > 0 {
for _, i := range r.cfg.SingleNameOrder {
if i > 0 && int(i) <= len(clientNames) {
result = []string{clientNames[i-1]}
2020-01-12 18:23:35 +01:00
break
2020-01-12 18:23:35 +01:00
}
}
} else {
result = clientNames
2020-01-12 18:23:35 +01:00
}
logger.WithField("client_names", strings.Join(result, "; ")).Debug("resolved client name(s) from external resolver")
2020-01-12 18:23:35 +01:00
return result
}
2020-05-17 21:50:40 +02:00
func (r *ClientNamesResolver) getNameFromIPMapping(ip net.IP, result []string) []string {
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
for name, ips := range r.cfg.ClientnameIPMapping {
2020-05-17 21:50:40 +02:00
for _, i := range ips {
if ip.String() == i.String() {
result = append(result, name)
}
}
}
return result
}
// FlushCache reset client name cache
2020-01-12 18:23:35 +01:00
func (r *ClientNamesResolver) FlushCache() {
r.cache.Clear()
2020-01-12 18:23:35 +01:00
}