143 lines
3.1 KiB
Go
143 lines
3.1 KiB
Go
package app
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func newDNSRunCooldown() *dnsRunCooldown {
|
|
enabled := true
|
|
switch strings.ToLower(strings.TrimSpace(os.Getenv("RESOLVE_DNS_COOLDOWN_ENABLED"))) {
|
|
case "0", "false", "no", "off":
|
|
enabled = false
|
|
}
|
|
c := &dnsRunCooldown{
|
|
enabled: enabled,
|
|
minAttempts: envInt("RESOLVE_DNS_COOLDOWN_MIN_ATTEMPTS", 300),
|
|
timeoutRatePct: envInt("RESOLVE_DNS_COOLDOWN_TIMEOUT_RATE_PCT", 70),
|
|
failStreak: envInt("RESOLVE_DNS_COOLDOWN_FAIL_STREAK", 25),
|
|
banSec: envInt("RESOLVE_DNS_COOLDOWN_BAN_SEC", 60),
|
|
maxBanSec: envInt("RESOLVE_DNS_COOLDOWN_MAX_BAN_SEC", 300),
|
|
temporaryAsError: true,
|
|
byUpstream: map[string]*dnsCooldownState{},
|
|
}
|
|
if c.minAttempts < 50 {
|
|
c.minAttempts = 50
|
|
}
|
|
if c.minAttempts > 2000 {
|
|
c.minAttempts = 2000
|
|
}
|
|
if c.timeoutRatePct < 40 {
|
|
c.timeoutRatePct = 40
|
|
}
|
|
if c.timeoutRatePct > 95 {
|
|
c.timeoutRatePct = 95
|
|
}
|
|
if c.failStreak < 8 {
|
|
c.failStreak = 8
|
|
}
|
|
if c.failStreak > 200 {
|
|
c.failStreak = 200
|
|
}
|
|
if c.banSec < 10 {
|
|
c.banSec = 10
|
|
}
|
|
if c.banSec > 3600 {
|
|
c.banSec = 3600
|
|
}
|
|
if c.maxBanSec < c.banSec {
|
|
c.maxBanSec = c.banSec
|
|
}
|
|
if c.maxBanSec > 3600 {
|
|
c.maxBanSec = 3600
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *dnsRunCooldown) configSnapshot() (enabled bool, minAttempts, timeoutRatePct, failStreak, banSec, maxBanSec int) {
|
|
if c == nil {
|
|
return false, 0, 0, 0, 0, 0
|
|
}
|
|
return c.enabled, c.minAttempts, c.timeoutRatePct, c.failStreak, c.banSec, c.maxBanSec
|
|
}
|
|
|
|
func (c *dnsRunCooldown) stateFor(upstream string) *dnsCooldownState {
|
|
if c.byUpstream == nil {
|
|
c.byUpstream = map[string]*dnsCooldownState{}
|
|
}
|
|
st, ok := c.byUpstream[upstream]
|
|
if ok {
|
|
return st
|
|
}
|
|
st = &dnsCooldownState{}
|
|
c.byUpstream[upstream] = st
|
|
return st
|
|
}
|
|
|
|
func (c *dnsRunCooldown) shouldSkip(upstream string, now int64) bool {
|
|
if c == nil || !c.enabled {
|
|
return false
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
st := c.stateFor(upstream)
|
|
return st.BanUntil > now
|
|
}
|
|
|
|
func (c *dnsRunCooldown) observeSuccess(upstream string) {
|
|
if c == nil || !c.enabled {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
st := c.stateFor(upstream)
|
|
st.Attempts++
|
|
st.FailStreak = 0
|
|
}
|
|
|
|
func (c *dnsRunCooldown) observeError(upstream string, kind dnsErrorKind, now int64) (bool, int) {
|
|
if c == nil || !c.enabled {
|
|
return false, 0
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
st := c.stateFor(upstream)
|
|
st.Attempts++
|
|
|
|
timeoutLike := kind == dnsErrorTimeout || (c.temporaryAsError && kind == dnsErrorTemporary)
|
|
if timeoutLike {
|
|
st.TimeoutLike++
|
|
st.FailStreak++
|
|
} else {
|
|
st.FailStreak = 0
|
|
return false, 0
|
|
}
|
|
if st.BanUntil > now {
|
|
return false, 0
|
|
}
|
|
|
|
rateBan := st.Attempts >= c.minAttempts && (st.TimeoutLike*100 >= c.timeoutRatePct*st.Attempts)
|
|
streakBan := st.FailStreak >= c.failStreak
|
|
if !rateBan && !streakBan {
|
|
return false, 0
|
|
}
|
|
|
|
st.BanLevel++
|
|
dur := c.banSec
|
|
if st.BanLevel > 1 {
|
|
for i := 1; i < st.BanLevel; i++ {
|
|
dur *= 2
|
|
if dur >= c.maxBanSec {
|
|
dur = c.maxBanSec
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if dur > c.maxBanSec {
|
|
dur = c.maxBanSec
|
|
}
|
|
st.BanUntil = now + int64(dur)
|
|
st.FailStreak = 0
|
|
return true, dur
|
|
}
|