Files
elmprodvpn/selective-vpn-api/app/resolver/resolve_batch.go

116 lines
2.7 KiB
Go

package resolver
type ResolveBatchInput struct {
ToResolve []string
Workers int
Now int
StaleKeepSec int
}
type ResolveBatchResult struct {
DNSStats DNSMetrics
ResolvedNowDNS int
ResolvedNowStale int
UnresolvedAfterAttempts int
StaleHitsDelta int
}
func ExecuteResolveBatch(
in ResolveBatchInput,
resolved map[string][]string,
domainCache *DomainCacheState,
cacheSourceForHost func(string) DomainCacheSource,
resolveHost func(string) ([]string, DNSMetrics),
logf func(string, ...any),
) ResolveBatchResult {
out := ResolveBatchResult{}
if len(in.ToResolve) == 0 || resolveHost == nil || domainCache == nil {
return out
}
workers := in.Workers
if workers < 1 {
workers = 1
}
if workers > 500 {
workers = 500
}
resolveSource := cacheSourceForHost
if resolveSource == nil {
resolveSource = func(string) DomainCacheSource { return DomainCacheSourceDirect }
}
type result struct {
host string
ips []string
stats DNSMetrics
}
jobs := make(chan string, len(in.ToResolve))
results := make(chan result, len(in.ToResolve))
for i := 0; i < workers; i++ {
go func() {
for host := range jobs {
ips, stats := resolveHost(host)
results <- result{host: host, ips: ips, stats: stats}
}
}()
}
for _, host := range in.ToResolve {
jobs <- host
}
close(jobs)
for i := 0; i < len(in.ToResolve); i++ {
r := <-results
out.DNSStats.Merge(r.stats)
hostErrors := r.stats.TotalErrors()
if hostErrors > 0 && logf != nil {
logf("resolve errors for %s: total=%d nxdomain=%d timeout=%d temporary=%d other=%d", r.host, hostErrors, r.stats.NXDomain, r.stats.Timeout, r.stats.Temporary, r.stats.Other)
}
if len(r.ips) > 0 {
if resolved != nil {
resolved[r.host] = r.ips
}
out.ResolvedNowDNS++
source := resolveSource(r.host)
domainCache.Set(r.host, source, r.ips, in.Now)
if logf != nil {
logf("%s -> %v", r.host, r.ips)
}
continue
}
staleApplied := false
if hostErrors > 0 {
source := resolveSource(r.host)
domainCache.SetErrorWithStats(r.host, source, r.stats, in.Now)
if in.StaleKeepSec > 0 && ShouldUseStaleOnError(r.stats) {
if staleIPs, staleAge, ok := domainCache.GetStale(r.host, source, in.Now, in.StaleKeepSec); ok {
out.StaleHitsDelta++
out.ResolvedNowStale++
staleApplied = true
if resolved != nil {
resolved[r.host] = staleIPs
}
if logf != nil {
logf("cache stale-keep (error)[age=%ds]: %s -> %v", staleAge, r.host, staleIPs)
}
}
}
}
if !staleApplied {
out.UnresolvedAfterAttempts++
}
if logf != nil && resolved != nil {
if _, ok := resolved[r.host]; !ok {
logf("%s: no IPs", r.host)
}
}
}
return out
}