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

120 lines
3.5 KiB
Go

package resolver
type ResolvePlanningInput struct {
Domains []string
Now int
TTL int
PrecheckDue bool
PrecheckMaxDomains int
StaleKeepSec int
NegTTLNX int
NegTTLTimeout int
NegTTLTemporary int
NegTTLOther int
}
type ResolvePlanningResult struct {
Fresh map[string][]string
ToResolve []string
CacheNegativeHits int
QuarantineHits int
StaleHits int
PrecheckScheduled int
}
func BuildResolvePlanning(
in ResolvePlanningInput,
domainCache *DomainCacheState,
cacheSourceForHost func(string) DomainCacheSource,
logf func(string, ...any),
) ResolvePlanningResult {
result := ResolvePlanningResult{
Fresh: map[string][]string{},
}
if domainCache == nil {
result.ToResolve = append(result.ToResolve, in.Domains...)
return result
}
resolveSource := cacheSourceForHost
if resolveSource == nil {
resolveSource = func(string) DomainCacheSource { return DomainCacheSourceDirect }
}
for _, d := range in.Domains {
source := resolveSource(d)
if ips, ok := domainCache.Get(d, source, in.Now, in.TTL); ok {
result.Fresh[d] = ips
if logf != nil {
logf("cache hit[%s]: %s -> %v", source, d, ips)
}
continue
}
// Quarantine has priority over negative TTL cache so 24h quarantine
// is not silently overridden by shorter negative cache windows.
if state, age, ok := domainCache.GetQuarantine(d, source, in.Now); ok {
kind, hasKind := domainCache.GetLastErrorKind(d, source)
timeoutKind := hasKind && kind == DNSErrorTimeout
if in.PrecheckDue && result.PrecheckScheduled < in.PrecheckMaxDomains {
// Timeout-based quarantine is rechecked in background batch and should
// not flood trace with per-domain debug lines.
if timeoutKind {
result.QuarantineHits++
if in.StaleKeepSec > 0 {
if staleIPs, staleAge, ok := domainCache.GetStale(d, source, in.Now, in.StaleKeepSec); ok {
result.StaleHits++
result.Fresh[d] = staleIPs
if logf != nil {
logf("cache stale-keep (quarantine)[age=%ds]: %s -> %v", staleAge, d, staleIPs)
}
}
}
continue
}
result.PrecheckScheduled++
result.ToResolve = append(result.ToResolve, d)
if logf != nil {
logf("precheck schedule[quarantine/%s age=%ds]: %s (%s)", state, age, d, source)
}
continue
}
result.QuarantineHits++
if logf != nil {
logf("cache quarantine hit[%s age=%ds]: %s (%s)", state, age, d, source)
}
if in.StaleKeepSec > 0 {
if staleIPs, staleAge, ok := domainCache.GetStale(d, source, in.Now, in.StaleKeepSec); ok {
result.StaleHits++
result.Fresh[d] = staleIPs
if logf != nil {
logf("cache stale-keep (quarantine)[age=%ds]: %s -> %v", staleAge, d, staleIPs)
}
}
}
continue
}
if kind, age, ok := domainCache.GetNegative(d, source, in.Now, in.NegTTLNX, in.NegTTLTimeout, in.NegTTLTemporary, in.NegTTLOther); ok {
if in.PrecheckDue && result.PrecheckScheduled < in.PrecheckMaxDomains {
if kind == DNSErrorTimeout {
result.CacheNegativeHits++
continue
}
result.PrecheckScheduled++
result.ToResolve = append(result.ToResolve, d)
if logf != nil {
logf("precheck schedule[negative/%s age=%ds]: %s (%s)", kind, age, d, source)
}
continue
}
result.CacheNegativeHits++
if logf != nil {
logf("cache neg hit[%s/%s age=%ds]: %s", source, kind, age, d)
}
continue
}
result.ToResolve = append(result.ToResolve, d)
}
return result
}