dns: switch to active upstream pool and wave fallback behavior
This commit is contained in:
@@ -31,11 +31,14 @@ const (
|
||||
routesCacheIPs = stateDir + "/routes-clear-cache-ips.txt"
|
||||
routesCacheDyn = stateDir + "/routes-clear-cache-ips-dyn.txt"
|
||||
routesCacheMap = stateDir + "/routes-clear-cache-ips-map.txt"
|
||||
routesCacheMapD = stateDir + "/routes-clear-cache-ips-map-direct.txt"
|
||||
routesCacheMapW = stateDir + "/routes-clear-cache-ips-map-wildcard.txt"
|
||||
routesCacheRT = stateDir + "/routes-clear-cache-routes.txt"
|
||||
|
||||
autoloopLogPath = stateDir + "/adguard-autoloop.log"
|
||||
loginStatePath = stateDir + "/adguard-login.json"
|
||||
dnsUpstreamsPath = stateDir + "/dns-upstreams.json"
|
||||
dnsUpstreamPool = stateDir + "/dns-upstream-pool.json"
|
||||
smartdnsWLPath = stateDir + "/smartdns-wildcards.json"
|
||||
smartdnsRTPath = stateDir + "/smartdns-runtime.json"
|
||||
desiredLocation = stateDir + "/adguard-location.txt"
|
||||
@@ -64,6 +67,7 @@ const (
|
||||
// RU: Дополнительные метки для per-app маршрутизации (systemd scope / cgroup).
|
||||
MARK_DIRECT = "0x67" // force direct (bypass VPN table even in full tunnel)
|
||||
MARK_APP = "0x68" // force VPN for app-scoped traffic (works even in traffic-mode=direct)
|
||||
MARK_INGRESS = "0x69" // keep ingress reply-path direct in full tunnel (server-safe)
|
||||
defaultDNS1 = "94.140.14.14"
|
||||
defaultDNS2 = "94.140.15.15"
|
||||
defaultMeta1 = "46.243.231.30"
|
||||
|
||||
@@ -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 из хранилища или конфига.
|
||||
|
||||
@@ -141,14 +141,9 @@ type wildcardMatcher struct {
|
||||
suffix []string
|
||||
}
|
||||
|
||||
var resolverFallbackDNS = []string{
|
||||
"1.1.1.1#53",
|
||||
"1.0.0.1#53",
|
||||
"9.9.9.9#53",
|
||||
"149.112.112.112#53",
|
||||
"8.8.8.8#53",
|
||||
"8.8.4.4#53",
|
||||
}
|
||||
// Empty by default: primary resolver pool comes from DNS upstream pool state.
|
||||
// Optional fallback list can still be provided via RESOLVE_DNS_FALLBACKS env.
|
||||
var resolverFallbackDNS []string
|
||||
|
||||
func normalizeWildcardDomain(raw string) string {
|
||||
d := strings.TrimSpace(strings.SplitN(raw, "#", 2)[0])
|
||||
@@ -562,14 +557,8 @@ func digA(host string, dnsList []string, timeout time.Duration, logf func(string
|
||||
if logf != nil {
|
||||
logf("dns warn %s via %s: kind=%s err=%v", host, addr, kind, err)
|
||||
}
|
||||
// NXDOMAIN usually means authoritative negative answer.
|
||||
// Do not fan out further retries for this host.
|
||||
if kind == dnsErrorNXDomain {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
stats.addSuccess(addr)
|
||||
var ips []string
|
||||
for _, ip := range records {
|
||||
if isPrivateIPv4(ip) {
|
||||
@@ -577,6 +566,14 @@ func digA(host string, dnsList []string, timeout time.Duration, logf func(string
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
stats.addError(addr, dnsErrorOther)
|
||||
if logf != nil {
|
||||
logf("dns warn %s via %s: kind=other err=no_public_ips", host, addr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
stats.addSuccess(addr)
|
||||
return uniqueStrings(ips), stats
|
||||
}
|
||||
return nil, stats
|
||||
@@ -1036,6 +1033,11 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
||||
SmartDNS: smartDNSAddr(),
|
||||
Mode: DNSModeDirect,
|
||||
}
|
||||
activePool := loadEnabledDNSUpstreamPool()
|
||||
if len(activePool) > 0 {
|
||||
cfg.Default = activePool
|
||||
cfg.Meta = activePool
|
||||
}
|
||||
|
||||
// 1) Если форсируем SmartDNS — вообще игнорим файл и ходим только через локальный резолвер.
|
||||
if smartDNSForced() {
|
||||
@@ -1051,12 +1053,14 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// 2) Иначе пытаемся прочитать dns-upstreams.conf, как и раньше.
|
||||
// 2) Читаем dns-upstreams.conf для legacy-совместимости и smartdns/mode значений.
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if logf != nil {
|
||||
logf("dns-config: use built-in defaults, can't read %s: %v", path, err)
|
||||
logf("dns-config: can't read %s: %v", path, err)
|
||||
}
|
||||
cfg.Default = mergeDNSUpstreamPools(cfg.Default, resolverFallbackPool())
|
||||
cfg.Meta = mergeDNSUpstreamPools(cfg.Meta, resolverFallbackPool())
|
||||
return cfg
|
||||
}
|
||||
|
||||
@@ -1098,11 +1102,13 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(def) > 0 {
|
||||
cfg.Default = def
|
||||
}
|
||||
if len(meta) > 0 {
|
||||
cfg.Meta = meta
|
||||
if len(activePool) == 0 {
|
||||
if len(def) > 0 {
|
||||
cfg.Default = def
|
||||
}
|
||||
if len(meta) > 0 {
|
||||
cfg.Meta = meta
|
||||
}
|
||||
}
|
||||
cfg.Default = mergeDNSUpstreamPools(cfg.Default, resolverFallbackPool())
|
||||
cfg.Meta = mergeDNSUpstreamPools(cfg.Meta, resolverFallbackPool())
|
||||
|
||||
@@ -169,6 +169,7 @@ func Run() {
|
||||
|
||||
// DNS upstreams
|
||||
mux.HandleFunc("/api/v1/dns-upstreams", handleDNSUpstreams)
|
||||
mux.HandleFunc("/api/v1/dns/upstream-pool", handleDNSUpstreamPool)
|
||||
mux.HandleFunc("/api/v1/dns/status", handleDNSStatus)
|
||||
mux.HandleFunc("/api/v1/dns/mode", handleDNSModeSet)
|
||||
mux.HandleFunc("/api/v1/dns/benchmark", handleDNSBenchmark)
|
||||
|
||||
@@ -46,6 +46,15 @@ type DNSUpstreams struct {
|
||||
Meta2 string `json:"meta2"`
|
||||
}
|
||||
|
||||
type DNSUpstreamPoolItem struct {
|
||||
Addr string `json:"addr"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type DNSUpstreamPoolState struct {
|
||||
Items []DNSUpstreamPoolItem `json:"items"`
|
||||
}
|
||||
|
||||
type DNSResolverMode string
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user