mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-12-20 22:11:50 +03:00
aab6769fa2
Squashed commit of the following: commit 695db57ec872ac8fa1b3a3b2c095bbe4c5c782cc Merge:6b3fda0a3
0b8bf1345
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Thu Aug 29 16:08:39 2024 +0300 Merge branch 'master' into AGDNS-2374-slog-rdns-whois commit6b3fda0a37
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Tue Aug 27 20:40:01 2024 +0300 all: imp code commit0b1f022094
Author: Stanislav Chzhen <s.chzhen@adguard.com> Date: Mon Aug 26 20:35:06 2024 +0300 all: slog rdns whois
147 lines
3.9 KiB
Go
147 lines
3.9 KiB
Go
// Package rdns processes reverse DNS lookup queries.
|
|
package rdns
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/netip"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
|
"github.com/bluele/gcache"
|
|
)
|
|
|
|
// Interface processes rDNS queries.
|
|
type Interface interface {
|
|
// Process makes rDNS request and returns domain name. changed indicates
|
|
// that domain name was updated since last request.
|
|
Process(ctx context.Context, ip netip.Addr) (host string, changed bool)
|
|
}
|
|
|
|
// Empty is an empty [Interface] implementation which does nothing.
|
|
type Empty struct{}
|
|
|
|
// type check
|
|
var _ Interface = (*Empty)(nil)
|
|
|
|
// Process implements the [Interface] interface for Empty.
|
|
func (Empty) Process(_ context.Context, _ netip.Addr) (host string, changed bool) {
|
|
return "", false
|
|
}
|
|
|
|
// Exchanger is a resolver for clients' addresses.
|
|
type Exchanger interface {
|
|
// Exchange tries to resolve the ip in a suitable way, i.e. either as local
|
|
// or as external.
|
|
Exchange(ip netip.Addr) (host string, ttl time.Duration, err error)
|
|
}
|
|
|
|
// Config is the configuration structure for Default.
|
|
type Config struct {
|
|
// Logger is used for logging the operation of the reverse DNS lookup
|
|
// queries. It must not be nil.
|
|
Logger *slog.Logger
|
|
|
|
// Exchanger resolves IP addresses to domain names.
|
|
Exchanger Exchanger
|
|
|
|
// CacheSize is the maximum size of the cache. It must be greater than
|
|
// zero.
|
|
CacheSize int
|
|
|
|
// CacheTTL is the Time to Live duration for cached IP addresses.
|
|
CacheTTL time.Duration
|
|
}
|
|
|
|
// Default is the default rDNS query processor.
|
|
type Default struct {
|
|
// logger is used for logging the operation of the reverse DNS lookup
|
|
// queries. It must not be nil.
|
|
logger *slog.Logger
|
|
|
|
// cache is the cache containing IP addresses of clients. An active IP
|
|
// address is resolved once again after it expires. If IP address couldn't
|
|
// be resolved, it stays here for some time to prevent further attempts to
|
|
// resolve the same IP.
|
|
cache gcache.Cache
|
|
|
|
// exchanger resolves IP addresses to domain names.
|
|
exchanger Exchanger
|
|
|
|
// cacheTTL is the Time to Live duration for cached IP addresses.
|
|
cacheTTL time.Duration
|
|
}
|
|
|
|
// New returns a new default rDNS query processor. conf must not be nil.
|
|
func New(conf *Config) (r *Default) {
|
|
return &Default{
|
|
logger: conf.Logger,
|
|
cache: gcache.New(conf.CacheSize).LRU().Build(),
|
|
exchanger: conf.Exchanger,
|
|
cacheTTL: conf.CacheTTL,
|
|
}
|
|
}
|
|
|
|
// type check
|
|
var _ Interface = (*Default)(nil)
|
|
|
|
// Process implements the [Interface] interface for Default.
|
|
func (r *Default) Process(ctx context.Context, ip netip.Addr) (host string, changed bool) {
|
|
fromCache, expired := r.findInCache(ctx, ip)
|
|
if !expired {
|
|
return fromCache, false
|
|
}
|
|
|
|
host, ttl, err := r.exchanger.Exchange(ip)
|
|
if err != nil {
|
|
r.logger.DebugContext(ctx, "resolving", "ip", ip, slogutil.KeyError, err)
|
|
}
|
|
|
|
ttl = max(ttl, r.cacheTTL)
|
|
|
|
item := &cacheItem{
|
|
expiry: time.Now().Add(ttl),
|
|
host: host,
|
|
}
|
|
|
|
err = r.cache.Set(ip, item)
|
|
if err != nil {
|
|
r.logger.DebugContext(ctx, "adding item to cache", "key", ip, slogutil.KeyError, err)
|
|
}
|
|
|
|
// TODO(e.burkov): The name doesn't change if it's neither stored in cache
|
|
// nor resolved successfully. Is it correct?
|
|
return host, fromCache == "" || host != fromCache
|
|
}
|
|
|
|
// findInCache finds domain name in the cache. expired is true if host is not
|
|
// valid anymore.
|
|
func (r *Default) findInCache(ctx context.Context, ip netip.Addr) (host string, expired bool) {
|
|
val, err := r.cache.Get(ip)
|
|
if err != nil {
|
|
if !errors.Is(err, gcache.KeyNotFoundError) {
|
|
r.logger.DebugContext(
|
|
ctx,
|
|
"retrieving item from cache",
|
|
"key", ip,
|
|
slogutil.KeyError, err,
|
|
)
|
|
}
|
|
|
|
return "", true
|
|
}
|
|
|
|
item := val.(*cacheItem)
|
|
|
|
return item.host, time.Now().After(item.expiry)
|
|
}
|
|
|
|
// cacheItem represents an item that we will store in the cache.
|
|
type cacheItem struct {
|
|
// expiry is the time when cacheItem will expire.
|
|
expiry time.Time
|
|
|
|
// host is the domain name of a runtime client.
|
|
host string
|
|
}
|