AdGuardHome/internal/rdns/rdns.go
Eugene Burkov 300821a7fb Pull request 1943: 6046 Local PTR
Merge in DNS/adguard-home from 6046-local-ptr to master

Updates #6046.

Squashed commit of the following:

commit 3e90815f29173d2f68970278bd7b1b29cc0a4465
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 27 18:17:41 2023 +0300

    all: log changes

commit 7639f6f785670c15911fb3ca20abeb4e2b8f8582
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jul 27 17:40:49 2023 +0300

    all: fix 0 ttl ptr
2023-07-27 18:23:23 +03:00

138 lines
3.6 KiB
Go

// Package rdns processes reverse DNS lookup queries.
package rdns
import (
"net/netip"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"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(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(_ 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 {
// 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 {
// 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{
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(ip netip.Addr) (host string, changed bool) {
fromCache, expired := r.findInCache(ip)
if !expired {
return fromCache, false
}
host, ttl, err := r.exchanger.Exchange(ip)
if err != nil {
log.Debug("rdns: resolving %q: %s", ip, err)
}
// TODO(s.chzhen): Use built-in function max in Go 1.21.
ttl = mathutil.Max(ttl, r.cacheTTL)
item := &cacheItem{
expiry: time.Now().Add(ttl),
host: host,
}
err = r.cache.Set(ip, item)
if err != nil {
log.Debug("rdns: cache: adding item %q: %s", ip, 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(ip netip.Addr) (host string, expired bool) {
val, err := r.cache.Get(ip)
if err != nil {
if !errors.Is(err, gcache.KeyNotFoundError) {
log.Debug("rdns: cache: retrieving %q: %s", ip, err)
}
return "", true
}
item, ok := val.(*cacheItem)
if !ok {
log.Debug("rdns: cache: %q bad type %T", ip, val)
return "", true
}
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
}