mirror of https://github.com/0xERR0R/blocky.git
174 lines
4.2 KiB
Go
174 lines
4.2 KiB
Go
package resolver
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/0xERR0R/blocky/cache/expirationcache"
|
|
"github.com/0xERR0R/blocky/config"
|
|
"github.com/0xERR0R/blocky/log"
|
|
"github.com/0xERR0R/blocky/model"
|
|
"github.com/0xERR0R/blocky/util"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ClientNamesResolver tries to determine client name by asking responsible DNS server via rDNS (reverse lookup)
|
|
type ClientNamesResolver struct {
|
|
configurable[*config.ClientLookup]
|
|
NextResolver
|
|
typed
|
|
|
|
cache expirationcache.ExpiringCache[[]string]
|
|
externalResolver Resolver
|
|
}
|
|
|
|
// NewClientNamesResolver creates new resolver instance
|
|
func NewClientNamesResolver(ctx context.Context,
|
|
cfg config.ClientLookup, upstreamsCfg config.Upstreams, bootstrap *Bootstrap,
|
|
) (cr *ClientNamesResolver, err error) {
|
|
var r Resolver
|
|
if !cfg.Upstream.IsDefault() {
|
|
r, err = NewUpstreamResolver(ctx, newUpstreamConfig(cfg.Upstream, upstreamsCfg), bootstrap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
cr = &ClientNamesResolver{
|
|
configurable: withConfig(&cfg),
|
|
typed: withType("client_names"),
|
|
|
|
cache: expirationcache.NewCache[[]string](ctx, expirationcache.Options{
|
|
CleanupInterval: time.Hour,
|
|
}),
|
|
externalResolver: r,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// LogConfig implements `config.Configurable`.
|
|
func (r *ClientNamesResolver) LogConfig(logger *logrus.Entry) {
|
|
r.cfg.LogConfig(logger)
|
|
|
|
logger.Infof("cache entries = %d", r.cache.TotalCount())
|
|
}
|
|
|
|
// 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)
|
|
|
|
request.ClientNames = clientNames
|
|
ctx, _ = log.CtxWithFields(ctx, logrus.Fields{"client_names": strings.Join(clientNames, "; ")})
|
|
|
|
return r.next.Resolve(ctx, request)
|
|
}
|
|
|
|
// returns names of client
|
|
func (r *ClientNamesResolver) getClientNames(ctx context.Context, request *model.Request) []string {
|
|
if request.RequestClientID != "" {
|
|
return []string{request.RequestClientID}
|
|
}
|
|
|
|
ip := request.ClientIP
|
|
if ip == nil {
|
|
return []string{}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
names := r.resolveClientNames(ctx, ip)
|
|
|
|
r.cache.Put(ip.String(), &names, time.Hour)
|
|
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
|
|
// try client mapping first
|
|
result = r.getNameFromIPMapping(ip, result)
|
|
if len(result) > 0 {
|
|
return result
|
|
}
|
|
|
|
if r.externalResolver == nil {
|
|
return []string{ip.String()}
|
|
}
|
|
|
|
reverse, _ := dns.ReverseAddr(ip.String())
|
|
|
|
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)
|
|
|
|
return []string{ip.String()}
|
|
}
|
|
|
|
clientNames := extractClientNamesFromAnswer(resp.Res.Answer, ip)
|
|
|
|
// optional: if singleNameOrder is set, use only one name in the defined order
|
|
if len(r.cfg.SingleNameOrder) > 0 {
|
|
for _, i := range r.cfg.SingleNameOrder {
|
|
if i > 0 && int(i) <= len(clientNames) {
|
|
result = []string{clientNames[i-1]}
|
|
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
result = clientNames
|
|
}
|
|
|
|
logger.WithField("client_names", strings.Join(result, "; ")).Debug("resolved client name(s) from external resolver")
|
|
|
|
return result
|
|
}
|
|
|
|
func (r *ClientNamesResolver) getNameFromIPMapping(ip net.IP, result []string) []string {
|
|
for name, ips := range r.cfg.ClientnameIPMapping {
|
|
for _, i := range ips {
|
|
if ip.String() == i.String() {
|
|
result = append(result, name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// FlushCache reset client name cache
|
|
func (r *ClientNamesResolver) FlushCache() {
|
|
r.cache.Clear()
|
|
}
|