AdGuardDNS/internal/agdnet/resolver.go

214 lines
5.1 KiB
Go
Raw Permalink Normal View History

2022-11-07 10:21:24 +03:00
package agdnet
import (
"context"
"fmt"
"math"
"net"
2023-09-06 08:22:07 +03:00
"net/netip"
2022-11-07 10:21:24 +03:00
"sync"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
)
// Resolver is the DNS resolver interface.
//
2024-01-04 19:22:32 +03:00
// See [net.Resolver].
//
// TODO(a.garipov): Move to golibs and unite with the one in module dnsproxy.
2022-11-07 10:21:24 +03:00
type Resolver interface {
2023-09-06 08:22:07 +03:00
// LookupNetIP returns a slice of host's IP addresses of family specified by
// fam, which must be either [netutil.AddrFamilyIPv4] or
// [netutil.AddrFamilyIPv6].
LookupNetIP(
ctx context.Context,
fam netutil.AddrFamily,
host string,
) (ips []netip.Addr, err error)
2022-11-07 10:21:24 +03:00
}
// DefaultResolver uses [net.DefaultResolver] to resolve addresses.
type DefaultResolver struct{}
// type check
var _ Resolver = DefaultResolver{}
2023-09-06 08:22:07 +03:00
// LookupNetIP implements the [Resolver] interface for DefaultResolver.
func (DefaultResolver) LookupNetIP(
2022-11-07 10:21:24 +03:00
ctx context.Context,
fam netutil.AddrFamily,
host string,
2023-09-06 08:22:07 +03:00
) (ips []netip.Addr, err error) {
2022-11-07 10:21:24 +03:00
switch fam {
case netutil.AddrFamilyIPv4:
2023-09-06 08:22:07 +03:00
return net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
2022-11-07 10:21:24 +03:00
case netutil.AddrFamilyIPv6:
2023-09-06 08:22:07 +03:00
return net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
2022-11-07 10:21:24 +03:00
default:
return nil, net.UnknownNetworkError(fam.String())
}
}
// resolveCache is a simple address resolving cache.
type resolveCache map[string]*resolveCacheItem
// resolveCacheItem is an item of [resolveCache].
type resolveCacheItem struct {
refrTime time.Time
2023-09-06 08:22:07 +03:00
ips []netip.Addr
2022-11-07 10:21:24 +03:00
}
// CachingResolver caches resolved results for hosts for a certain time,
// regardless of the actual TTLs of the records. It is used for caching the
// results of lookups of hostnames that don't change their IP addresses often.
type CachingResolver struct {
resolver Resolver
// mu protects ip4 and ip6.
mu *sync.Mutex
ipv4 resolveCache
ipv6 resolveCache
ttl time.Duration
}
// NewCachingResolver returns a new caching resolver.
func NewCachingResolver(resolver Resolver, ttl time.Duration) (c *CachingResolver) {
return &CachingResolver{
resolver: resolver,
mu: &sync.Mutex{},
ipv4: resolveCache{},
ipv6: resolveCache{},
ttl: ttl,
}
}
// type check
var _ Resolver = (*CachingResolver)(nil)
2023-09-06 08:22:07 +03:00
// LookupNetIP implements the [Resolver] interface for *CachingResolver. host
2022-11-07 10:21:24 +03:00
// should be normalized. Slice ips and its elements must not be mutated.
2023-09-06 08:22:07 +03:00
func (c *CachingResolver) LookupNetIP(
2022-11-07 10:21:24 +03:00
ctx context.Context,
fam netutil.AddrFamily,
host string,
2023-09-06 08:22:07 +03:00
) (ips []netip.Addr, err error) {
2022-11-07 10:21:24 +03:00
c.mu.Lock()
defer c.mu.Unlock()
var item *resolveCacheItem
switch fam {
case netutil.AddrFamilyIPv4:
item = c.ipv4[host]
case netutil.AddrFamilyIPv6:
item = c.ipv6[host]
default:
return nil, net.UnknownNetworkError(fam.String())
}
if item == nil || time.Since(item.refrTime) > c.ttl {
item, err = c.resolve(ctx, fam, host)
if err != nil {
return nil, err
}
}
return item.ips, nil
}
// resolve looks up the IP addresses for host and puts them into the cache.
func (c *CachingResolver) resolve(
ctx context.Context,
fam netutil.AddrFamily,
host string,
) (item *resolveCacheItem, err error) {
2023-09-06 08:22:07 +03:00
var ips []netip.Addr
2022-11-07 10:21:24 +03:00
refrTime := time.Now()
// Don't resolve IP addresses.
2023-08-08 18:31:48 +03:00
ip := ipFromHost(host, fam)
2023-09-06 08:22:07 +03:00
if ip != (netip.Addr{}) {
ips = []netip.Addr{ip}
2022-11-07 10:21:24 +03:00
// Set the refresh time to the maximum date that time.Duration allows to
// prevent this item from refreshing.
refrTime = time.Unix(0, math.MaxInt64)
} else {
2023-09-06 08:22:07 +03:00
ips, err = c.resolver.LookupNetIP(ctx, fam, host)
2022-11-07 10:21:24 +03:00
if err != nil {
if !isExpectedLookupError(fam, err) {
return nil, fmt.Errorf("resolving %s addr for %q: %w", fam, host, err)
}
log.Debug("caching resolver: warning: %s", err)
}
}
var cache resolveCache
if fam == netutil.AddrFamilyIPv4 {
cache = c.ipv4
} else {
cache = c.ipv6
}
item = &resolveCacheItem{
refrTime: refrTime,
ips: ips,
}
cache[host] = item
return item, nil
}
2023-09-06 08:22:07 +03:00
// ipFromHost parses host as if it'd be an IP address of specified fam. It
// returns an empty netip.
func ipFromHost(host string, fam netutil.AddrFamily) (ip netip.Addr) {
var famFunc func(netip.Addr) (ok bool)
switch fam {
case netutil.AddrFamilyIPv4:
famFunc = netip.Addr.Is4
case netutil.AddrFamilyIPv6:
famFunc = netip.Addr.Is6
default:
return netip.Addr{}
2023-08-08 18:31:48 +03:00
}
2023-09-06 08:22:07 +03:00
ip, err := netip.ParseAddr(host)
if err != nil || !famFunc(ip) {
return netip.Addr{}
2023-08-08 18:31:48 +03:00
}
2023-09-06 08:22:07 +03:00
return ip
2023-08-08 18:31:48 +03:00
}
2022-11-07 10:21:24 +03:00
// isExpectedLookupError returns true if the error is an expected lookup error.
func isExpectedLookupError(fam netutil.AddrFamily, err error) (ok bool) {
var dnsErr *net.DNSError
if fam == netutil.AddrFamilyIPv6 && errors.As(err, &dnsErr) {
// It's expected that Go default DNS resolver returns a DNS error in
// some cases when it receives an empty response. It's unclear what
// exactly triggers this error, though.
//
// TODO(ameshkov): Consider researching this in detail.
return true
}
var addrErr *net.AddrError
if !errors.As(err, &addrErr) {
return false
}
// Expect the error about no suitable addresses. For example, no IPv6
// addresses for a host that does have IPv4 ones.
//
// See function filterAddrList in ${GOROOT}/src/net/ipsock.go.
return addrErr.Err == "no suitable address found"
}