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

128 lines
2.6 KiB
Go

package resolver
import "strings"
func RunTimeoutQuarantineRecheck(
domains []string,
now int,
limit int,
workers int,
domainCache *DomainCacheState,
cacheSourceForHost func(string) DomainCacheSource,
resolveHost func(string) ([]string, DNSMetrics),
) ResolverTimeoutRecheckStats {
stats := ResolverTimeoutRecheckStats{}
if limit <= 0 || now <= 0 || domainCache == nil || resolveHost == nil {
return stats
}
if workers < 1 {
workers = 1
}
if workers > 200 {
workers = 200
}
resolveSource := cacheSourceForHost
if resolveSource == nil {
resolveSource = func(string) DomainCacheSource { return DomainCacheSourceDirect }
}
seen := map[string]struct{}{}
capHint := len(domains)
if capHint > limit {
capHint = limit
}
candidates := make([]string, 0, capHint)
for _, raw := range domains {
host := strings.TrimSpace(strings.ToLower(raw))
if host == "" {
continue
}
if _, ok := seen[host]; ok {
continue
}
seen[host] = struct{}{}
source := resolveSource(host)
if _, _, ok := domainCache.GetQuarantine(host, source, now); !ok {
continue
}
kind, ok := domainCache.GetLastErrorKind(host, source)
if !ok || kind != DNSErrorTimeout {
continue
}
candidates = append(candidates, host)
if len(candidates) >= limit {
break
}
}
if len(candidates) == 0 {
return stats
}
recoveredIPSet := map[string]struct{}{}
type result struct {
host string
source DomainCacheSource
ips []string
dns DNSMetrics
}
jobs := make(chan string, len(candidates))
results := make(chan result, len(candidates))
for i := 0; i < workers; i++ {
go func() {
for host := range jobs {
src := resolveSource(host)
ips, dnsStats := resolveHost(host)
results <- result{host: host, source: src, ips: ips, dns: dnsStats}
}
}()
}
for _, host := range candidates {
jobs <- host
}
close(jobs)
for i := 0; i < len(candidates); i++ {
r := <-results
stats.Checked++
if len(r.ips) > 0 {
for _, ip := range r.ips {
ip = strings.TrimSpace(ip)
if ip == "" {
continue
}
recoveredIPSet[ip] = struct{}{}
}
domainCache.Set(r.host, r.source, r.ips, now)
stats.Recovered++
continue
}
if r.dns.TotalErrors() > 0 {
domainCache.SetErrorWithStats(r.host, r.source, r.dns, now)
}
kind, ok := ClassifyHostErrorKind(r.dns)
if !ok {
stats.NoSignal++
continue
}
switch kind {
case DNSErrorTimeout:
stats.StillTimeout++
case DNSErrorNXDomain:
stats.NowNXDomain++
case DNSErrorTemporary:
stats.NowTemporary++
default:
stats.NowOther++
}
}
stats.RecoveredIPs = len(recoveredIPSet)
return stats
}