dns: switch to active upstream pool and wave fallback behavior

This commit is contained in:
beckline
2026-02-22 19:15:37 +03:00
parent a7ec4fe801
commit 0f88cfeeaa
9 changed files with 382 additions and 130 deletions

View File

@@ -56,6 +56,30 @@ func handleDNSUpstreams(w http.ResponseWriter, r *http.Request) {
}
}
func handleDNSUpstreamPool(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
items := loadDNSUpstreamPoolState()
writeJSON(w, http.StatusOK, DNSUpstreamPoolState{Items: items})
case http.MethodPost:
var body DNSUpstreamPoolState
if r.Body != nil {
defer r.Body.Close()
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&body); err != nil && err != io.EOF {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
}
if err := saveDNSUpstreamPoolState(body.Items); err != nil {
http.Error(w, "write error", http.StatusInternalServerError)
return
}
writeJSON(w, http.StatusOK, DNSUpstreamPoolState{Items: loadDNSUpstreamPoolState()})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
// ---------------------------------------------------------------------
// EN: `handleDNSStatus` is an HTTP handler for dns status.
// RU: `handleDNSStatus` - HTTP-обработчик для dns status.
@@ -95,13 +119,23 @@ func handleDNSBenchmark(w http.ResponseWriter, r *http.Request) {
upstreams := normalizeBenchmarkUpstreams(req.Upstreams)
if len(upstreams) == 0 {
cfg := loadDNSUpstreamsConf()
upstreams = normalizeBenchmarkUpstreamStrings([]string{
cfg.Default1,
cfg.Default2,
cfg.Meta1,
cfg.Meta2,
})
pool := loadDNSUpstreamPoolState()
if len(pool) > 0 {
tmp := make([]DNSBenchmarkUpstream, 0, len(pool))
for _, item := range pool {
tmp = append(tmp, DNSBenchmarkUpstream{Addr: item.Addr, Enabled: item.Enabled})
}
upstreams = normalizeBenchmarkUpstreams(tmp)
}
if len(upstreams) == 0 {
cfg := loadDNSUpstreamsConf()
upstreams = normalizeBenchmarkUpstreamStrings([]string{
cfg.Default1,
cfg.Default2,
cfg.Meta1,
cfg.Meta2,
})
}
}
if len(upstreams) == 0 {
http.Error(w, "no upstreams", http.StatusBadRequest)
@@ -197,9 +231,6 @@ func normalizeBenchmarkUpstreams(in []DNSBenchmarkUpstream) []string {
out := make([]string, 0, len(in))
seen := map[string]struct{}{}
for _, item := range in {
if !item.Enabled {
continue
}
n := normalizeDNSUpstream(item.Addr, "53")
if n == "" {
continue
@@ -842,11 +873,7 @@ func prewarmAggressiveFromEnv() bool {
}
}
// ---------------------------------------------------------------------
// EN: `loadDNSUpstreamsConf` loads dns upstreams conf from storage or config.
// RU: `loadDNSUpstreamsConf` - загружает dns upstreams conf из хранилища или конфига.
// ---------------------------------------------------------------------
func loadDNSUpstreamsConf() DNSUpstreams {
func loadDNSUpstreamsConfFile() DNSUpstreams {
cfg := DNSUpstreams{
Default1: defaultDNS1,
Default2: defaultDNS2,
@@ -903,11 +930,139 @@ func loadDNSUpstreamsConf() DNSUpstreams {
return cfg
}
// ---------------------------------------------------------------------
// EN: `saveDNSUpstreamsConf` saves dns upstreams conf to persistent storage.
// RU: `saveDNSUpstreamsConf` - сохраняет dns upstreams conf в постоянное хранилище.
// ---------------------------------------------------------------------
func saveDNSUpstreamsConf(cfg DNSUpstreams) error {
func normalizeDNSUpstreamPoolItems(items []DNSUpstreamPoolItem) []DNSUpstreamPoolItem {
out := make([]DNSUpstreamPoolItem, 0, len(items))
seen := map[string]struct{}{}
for _, item := range items {
addr := normalizeDNSUpstream(item.Addr, "53")
if addr == "" {
continue
}
if _, ok := seen[addr]; ok {
continue
}
seen[addr] = struct{}{}
out = append(out, DNSUpstreamPoolItem{
Addr: addr,
Enabled: item.Enabled,
})
}
return out
}
func dnsUpstreamPoolFromLegacy(cfg DNSUpstreams) []DNSUpstreamPoolItem {
raw := []string{cfg.Default1, cfg.Default2, cfg.Meta1, cfg.Meta2}
out := make([]DNSUpstreamPoolItem, 0, len(raw))
for _, item := range raw {
n := normalizeDNSUpstream(item, "53")
if n == "" {
continue
}
out = append(out, DNSUpstreamPoolItem{Addr: n, Enabled: true})
}
return normalizeDNSUpstreamPoolItems(out)
}
func dnsUpstreamPoolToLegacy(items []DNSUpstreamPoolItem) DNSUpstreams {
enabled := make([]string, 0, len(items))
all := make([]string, 0, len(items))
for _, item := range items {
n := normalizeDNSUpstream(item.Addr, "53")
if n == "" {
continue
}
all = append(all, n)
if item.Enabled {
enabled = append(enabled, n)
}
}
list := enabled
if len(list) == 0 {
list = all
}
if len(list) == 0 {
list = []string{defaultDNS1, defaultDNS2, defaultMeta1, defaultMeta2}
}
pick := func(idx int, fallback string) string {
if len(list) == 0 {
return fallback
}
if idx < len(list) {
return list[idx]
}
return list[idx%len(list)]
}
return DNSUpstreams{
Default1: pick(0, defaultDNS1),
Default2: pick(1, defaultDNS2),
Meta1: pick(2, defaultMeta1),
Meta2: pick(3, defaultMeta2),
}
}
func saveDNSUpstreamPoolFile(items []DNSUpstreamPoolItem) error {
state := DNSUpstreamPoolState{Items: normalizeDNSUpstreamPoolItems(items)}
if err := os.MkdirAll(filepath.Dir(dnsUpstreamPool), 0o755); err != nil {
return err
}
tmp := dnsUpstreamPool + ".tmp"
b, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(tmp, b, 0o644); err != nil {
return err
}
return os.Rename(tmp, dnsUpstreamPool)
}
func loadDNSUpstreamPoolState() []DNSUpstreamPoolItem {
data, err := os.ReadFile(dnsUpstreamPool)
if err == nil {
var st DNSUpstreamPoolState
if json.Unmarshal(data, &st) == nil {
items := normalizeDNSUpstreamPoolItems(st.Items)
if len(items) > 0 {
return items
}
}
}
legacy := loadDNSUpstreamsConfFile()
items := dnsUpstreamPoolFromLegacy(legacy)
if len(items) > 0 {
_ = saveDNSUpstreamPoolFile(items)
}
return items
}
func saveDNSUpstreamPoolState(items []DNSUpstreamPoolItem) error {
items = normalizeDNSUpstreamPoolItems(items)
if len(items) == 0 {
items = dnsUpstreamPoolFromLegacy(loadDNSUpstreamsConfFile())
}
if err := saveDNSUpstreamPoolFile(items); err != nil {
return err
}
return saveDNSUpstreamsConfFile(dnsUpstreamPoolToLegacy(items))
}
func loadEnabledDNSUpstreamPool() []string {
items := loadDNSUpstreamPoolState()
out := make([]string, 0, len(items))
for _, item := range items {
if !item.Enabled {
continue
}
n := normalizeDNSUpstream(item.Addr, "53")
if n == "" {
continue
}
out = append(out, n)
}
return uniqueStrings(out)
}
func saveDNSUpstreamsConfFile(cfg DNSUpstreams) error {
cfg.Default1 = normalizeDNSUpstream(cfg.Default1, "53")
cfg.Default2 = normalizeDNSUpstream(cfg.Default2, "53")
cfg.Meta1 = normalizeDNSUpstream(cfg.Meta1, "53")
@@ -947,10 +1102,32 @@ func saveDNSUpstreamsConf(cfg DNSUpstreams) error {
if b, err := json.MarshalIndent(cfg, "", " "); err == nil {
_ = os.WriteFile(dnsUpstreamsPath, b, 0o644)
}
return nil
}
// ---------------------------------------------------------------------
// EN: `loadDNSUpstreamsConf` loads dns upstreams conf from storage or config.
// RU: `loadDNSUpstreamsConf` - загружает dns upstreams conf из хранилища или конфига.
// ---------------------------------------------------------------------
func loadDNSUpstreamsConf() DNSUpstreams {
pool := loadDNSUpstreamPoolState()
if len(pool) > 0 {
return dnsUpstreamPoolToLegacy(pool)
}
return loadDNSUpstreamsConfFile()
}
// ---------------------------------------------------------------------
// EN: `saveDNSUpstreamsConf` saves dns upstreams conf to persistent storage.
// RU: `saveDNSUpstreamsConf` - сохраняет dns upstreams conf в постоянное хранилище.
// ---------------------------------------------------------------------
func saveDNSUpstreamsConf(cfg DNSUpstreams) error {
if err := saveDNSUpstreamsConfFile(cfg); err != nil {
return err
}
return saveDNSUpstreamPoolFile(dnsUpstreamPoolFromLegacy(cfg))
}
// ---------------------------------------------------------------------
// EN: `loadDNSMode` loads dns mode from storage or config.
// RU: `loadDNSMode` - загружает dns mode из хранилища или конфига.