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"
|
routesCacheIPs = stateDir + "/routes-clear-cache-ips.txt"
|
||||||
routesCacheDyn = stateDir + "/routes-clear-cache-ips-dyn.txt"
|
routesCacheDyn = stateDir + "/routes-clear-cache-ips-dyn.txt"
|
||||||
routesCacheMap = stateDir + "/routes-clear-cache-ips-map.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"
|
routesCacheRT = stateDir + "/routes-clear-cache-routes.txt"
|
||||||
|
|
||||||
autoloopLogPath = stateDir + "/adguard-autoloop.log"
|
autoloopLogPath = stateDir + "/adguard-autoloop.log"
|
||||||
loginStatePath = stateDir + "/adguard-login.json"
|
loginStatePath = stateDir + "/adguard-login.json"
|
||||||
dnsUpstreamsPath = stateDir + "/dns-upstreams.json"
|
dnsUpstreamsPath = stateDir + "/dns-upstreams.json"
|
||||||
|
dnsUpstreamPool = stateDir + "/dns-upstream-pool.json"
|
||||||
smartdnsWLPath = stateDir + "/smartdns-wildcards.json"
|
smartdnsWLPath = stateDir + "/smartdns-wildcards.json"
|
||||||
smartdnsRTPath = stateDir + "/smartdns-runtime.json"
|
smartdnsRTPath = stateDir + "/smartdns-runtime.json"
|
||||||
desiredLocation = stateDir + "/adguard-location.txt"
|
desiredLocation = stateDir + "/adguard-location.txt"
|
||||||
@@ -64,6 +67,7 @@ const (
|
|||||||
// RU: Дополнительные метки для per-app маршрутизации (systemd scope / cgroup).
|
// RU: Дополнительные метки для per-app маршрутизации (systemd scope / cgroup).
|
||||||
MARK_DIRECT = "0x67" // force direct (bypass VPN table even in full tunnel)
|
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_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"
|
defaultDNS1 = "94.140.14.14"
|
||||||
defaultDNS2 = "94.140.15.15"
|
defaultDNS2 = "94.140.15.15"
|
||||||
defaultMeta1 = "46.243.231.30"
|
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.
|
// EN: `handleDNSStatus` is an HTTP handler for dns status.
|
||||||
// RU: `handleDNSStatus` - HTTP-обработчик для dns status.
|
// RU: `handleDNSStatus` - HTTP-обработчик для dns status.
|
||||||
@@ -95,13 +119,23 @@ func handleDNSBenchmark(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
upstreams := normalizeBenchmarkUpstreams(req.Upstreams)
|
upstreams := normalizeBenchmarkUpstreams(req.Upstreams)
|
||||||
if len(upstreams) == 0 {
|
if len(upstreams) == 0 {
|
||||||
cfg := loadDNSUpstreamsConf()
|
pool := loadDNSUpstreamPoolState()
|
||||||
upstreams = normalizeBenchmarkUpstreamStrings([]string{
|
if len(pool) > 0 {
|
||||||
cfg.Default1,
|
tmp := make([]DNSBenchmarkUpstream, 0, len(pool))
|
||||||
cfg.Default2,
|
for _, item := range pool {
|
||||||
cfg.Meta1,
|
tmp = append(tmp, DNSBenchmarkUpstream{Addr: item.Addr, Enabled: item.Enabled})
|
||||||
cfg.Meta2,
|
}
|
||||||
})
|
upstreams = normalizeBenchmarkUpstreams(tmp)
|
||||||
|
}
|
||||||
|
if len(upstreams) == 0 {
|
||||||
|
cfg := loadDNSUpstreamsConf()
|
||||||
|
upstreams = normalizeBenchmarkUpstreamStrings([]string{
|
||||||
|
cfg.Default1,
|
||||||
|
cfg.Default2,
|
||||||
|
cfg.Meta1,
|
||||||
|
cfg.Meta2,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(upstreams) == 0 {
|
if len(upstreams) == 0 {
|
||||||
http.Error(w, "no upstreams", http.StatusBadRequest)
|
http.Error(w, "no upstreams", http.StatusBadRequest)
|
||||||
@@ -197,9 +231,6 @@ func normalizeBenchmarkUpstreams(in []DNSBenchmarkUpstream) []string {
|
|||||||
out := make([]string, 0, len(in))
|
out := make([]string, 0, len(in))
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
for _, item := range in {
|
for _, item := range in {
|
||||||
if !item.Enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n := normalizeDNSUpstream(item.Addr, "53")
|
n := normalizeDNSUpstream(item.Addr, "53")
|
||||||
if n == "" {
|
if n == "" {
|
||||||
continue
|
continue
|
||||||
@@ -842,11 +873,7 @@ func prewarmAggressiveFromEnv() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
func loadDNSUpstreamsConfFile() DNSUpstreams {
|
||||||
// EN: `loadDNSUpstreamsConf` loads dns upstreams conf from storage or config.
|
|
||||||
// RU: `loadDNSUpstreamsConf` - загружает dns upstreams conf из хранилища или конфига.
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
func loadDNSUpstreamsConf() DNSUpstreams {
|
|
||||||
cfg := DNSUpstreams{
|
cfg := DNSUpstreams{
|
||||||
Default1: defaultDNS1,
|
Default1: defaultDNS1,
|
||||||
Default2: defaultDNS2,
|
Default2: defaultDNS2,
|
||||||
@@ -903,11 +930,139 @@ func loadDNSUpstreamsConf() DNSUpstreams {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
func normalizeDNSUpstreamPoolItems(items []DNSUpstreamPoolItem) []DNSUpstreamPoolItem {
|
||||||
// EN: `saveDNSUpstreamsConf` saves dns upstreams conf to persistent storage.
|
out := make([]DNSUpstreamPoolItem, 0, len(items))
|
||||||
// RU: `saveDNSUpstreamsConf` - сохраняет dns upstreams conf в постоянное хранилище.
|
seen := map[string]struct{}{}
|
||||||
// ---------------------------------------------------------------------
|
for _, item := range items {
|
||||||
func saveDNSUpstreamsConf(cfg DNSUpstreams) error {
|
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.Default1 = normalizeDNSUpstream(cfg.Default1, "53")
|
||||||
cfg.Default2 = normalizeDNSUpstream(cfg.Default2, "53")
|
cfg.Default2 = normalizeDNSUpstream(cfg.Default2, "53")
|
||||||
cfg.Meta1 = normalizeDNSUpstream(cfg.Meta1, "53")
|
cfg.Meta1 = normalizeDNSUpstream(cfg.Meta1, "53")
|
||||||
@@ -947,10 +1102,32 @@ func saveDNSUpstreamsConf(cfg DNSUpstreams) error {
|
|||||||
if b, err := json.MarshalIndent(cfg, "", " "); err == nil {
|
if b, err := json.MarshalIndent(cfg, "", " "); err == nil {
|
||||||
_ = os.WriteFile(dnsUpstreamsPath, b, 0o644)
|
_ = os.WriteFile(dnsUpstreamsPath, b, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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.
|
// EN: `loadDNSMode` loads dns mode from storage or config.
|
||||||
// RU: `loadDNSMode` - загружает dns mode из хранилища или конфига.
|
// RU: `loadDNSMode` - загружает dns mode из хранилища или конфига.
|
||||||
|
|||||||
@@ -141,14 +141,9 @@ type wildcardMatcher struct {
|
|||||||
suffix []string
|
suffix []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolverFallbackDNS = []string{
|
// Empty by default: primary resolver pool comes from DNS upstream pool state.
|
||||||
"1.1.1.1#53",
|
// Optional fallback list can still be provided via RESOLVE_DNS_FALLBACKS env.
|
||||||
"1.0.0.1#53",
|
var resolverFallbackDNS []string
|
||||||
"9.9.9.9#53",
|
|
||||||
"149.112.112.112#53",
|
|
||||||
"8.8.8.8#53",
|
|
||||||
"8.8.4.4#53",
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeWildcardDomain(raw string) string {
|
func normalizeWildcardDomain(raw string) string {
|
||||||
d := strings.TrimSpace(strings.SplitN(raw, "#", 2)[0])
|
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 {
|
if logf != nil {
|
||||||
logf("dns warn %s via %s: kind=%s err=%v", host, addr, kind, err)
|
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
|
continue
|
||||||
}
|
}
|
||||||
stats.addSuccess(addr)
|
|
||||||
var ips []string
|
var ips []string
|
||||||
for _, ip := range records {
|
for _, ip := range records {
|
||||||
if isPrivateIPv4(ip) {
|
if isPrivateIPv4(ip) {
|
||||||
@@ -577,6 +566,14 @@ func digA(host string, dnsList []string, timeout time.Duration, logf func(string
|
|||||||
}
|
}
|
||||||
ips = append(ips, ip)
|
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 uniqueStrings(ips), stats
|
||||||
}
|
}
|
||||||
return nil, stats
|
return nil, stats
|
||||||
@@ -1036,6 +1033,11 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
|||||||
SmartDNS: smartDNSAddr(),
|
SmartDNS: smartDNSAddr(),
|
||||||
Mode: DNSModeDirect,
|
Mode: DNSModeDirect,
|
||||||
}
|
}
|
||||||
|
activePool := loadEnabledDNSUpstreamPool()
|
||||||
|
if len(activePool) > 0 {
|
||||||
|
cfg.Default = activePool
|
||||||
|
cfg.Meta = activePool
|
||||||
|
}
|
||||||
|
|
||||||
// 1) Если форсируем SmartDNS — вообще игнорим файл и ходим только через локальный резолвер.
|
// 1) Если форсируем SmartDNS — вообще игнорим файл и ходим только через локальный резолвер.
|
||||||
if smartDNSForced() {
|
if smartDNSForced() {
|
||||||
@@ -1051,12 +1053,14 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Иначе пытаемся прочитать dns-upstreams.conf, как и раньше.
|
// 2) Читаем dns-upstreams.conf для legacy-совместимости и smartdns/mode значений.
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if logf != 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
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,11 +1102,13 @@ func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(def) > 0 {
|
if len(activePool) == 0 {
|
||||||
cfg.Default = def
|
if len(def) > 0 {
|
||||||
}
|
cfg.Default = def
|
||||||
if len(meta) > 0 {
|
}
|
||||||
cfg.Meta = meta
|
if len(meta) > 0 {
|
||||||
|
cfg.Meta = meta
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cfg.Default = mergeDNSUpstreamPools(cfg.Default, resolverFallbackPool())
|
cfg.Default = mergeDNSUpstreamPools(cfg.Default, resolverFallbackPool())
|
||||||
cfg.Meta = mergeDNSUpstreamPools(cfg.Meta, resolverFallbackPool())
|
cfg.Meta = mergeDNSUpstreamPools(cfg.Meta, resolverFallbackPool())
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ func Run() {
|
|||||||
|
|
||||||
// DNS upstreams
|
// DNS upstreams
|
||||||
mux.HandleFunc("/api/v1/dns-upstreams", handleDNSUpstreams)
|
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/status", handleDNSStatus)
|
||||||
mux.HandleFunc("/api/v1/dns/mode", handleDNSModeSet)
|
mux.HandleFunc("/api/v1/dns/mode", handleDNSModeSet)
|
||||||
mux.HandleFunc("/api/v1/dns/benchmark", handleDNSBenchmark)
|
mux.HandleFunc("/api/v1/dns/benchmark", handleDNSBenchmark)
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ type DNSUpstreams struct {
|
|||||||
Meta2 string `json:"meta2"`
|
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
|
type DNSResolverMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -259,6 +259,11 @@ class DNSBenchmarkResponse:
|
|||||||
recommended_meta: List[str]
|
recommended_meta: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DNSUpstreamPoolState:
|
||||||
|
items: List[DNSBenchmarkUpstream]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SmartdnsServiceState:
|
class SmartdnsServiceState:
|
||||||
state: str
|
state: str
|
||||||
@@ -1185,6 +1190,48 @@ class ApiClient:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def dns_upstream_pool_get(self) -> DNSUpstreamPoolState:
|
||||||
|
data = cast(Dict[str, Any], self._json(self._request("GET", "/api/v1/dns/upstream-pool")) or {})
|
||||||
|
raw = data.get("items") or []
|
||||||
|
if not isinstance(raw, list):
|
||||||
|
raw = []
|
||||||
|
items: List[DNSBenchmarkUpstream] = []
|
||||||
|
for row in raw:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
addr = str(row.get("addr") or "").strip()
|
||||||
|
if not addr:
|
||||||
|
continue
|
||||||
|
items.append(DNSBenchmarkUpstream(addr=addr, enabled=bool(row.get("enabled", True))))
|
||||||
|
return DNSUpstreamPoolState(items=items)
|
||||||
|
|
||||||
|
def dns_upstream_pool_set(self, items: List[DNSBenchmarkUpstream]) -> DNSUpstreamPoolState:
|
||||||
|
data = cast(
|
||||||
|
Dict[str, Any],
|
||||||
|
self._json(
|
||||||
|
self._request(
|
||||||
|
"POST",
|
||||||
|
"/api/v1/dns/upstream-pool",
|
||||||
|
json_body={
|
||||||
|
"items": [{"addr": u.addr, "enabled": bool(u.enabled)} for u in (items or [])],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
or {},
|
||||||
|
)
|
||||||
|
raw = data.get("items") or []
|
||||||
|
if not isinstance(raw, list):
|
||||||
|
raw = []
|
||||||
|
out: List[DNSBenchmarkUpstream] = []
|
||||||
|
for row in raw:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
addr = str(row.get("addr") or "").strip()
|
||||||
|
if not addr:
|
||||||
|
continue
|
||||||
|
out.append(DNSBenchmarkUpstream(addr=addr, enabled=bool(row.get("enabled", True))))
|
||||||
|
return DNSUpstreamPoolState(items=out)
|
||||||
|
|
||||||
def dns_benchmark(
|
def dns_benchmark(
|
||||||
self,
|
self,
|
||||||
upstreams: List[DNSBenchmarkUpstream],
|
upstreams: List[DNSBenchmarkUpstream],
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from api_client import (
|
|||||||
CmdResult,
|
CmdResult,
|
||||||
DNSBenchmarkResponse,
|
DNSBenchmarkResponse,
|
||||||
DNSBenchmarkUpstream,
|
DNSBenchmarkUpstream,
|
||||||
|
DNSUpstreamPoolState,
|
||||||
DNSStatus,
|
DNSStatus,
|
||||||
DnsUpstreams,
|
DnsUpstreams,
|
||||||
DomainsFile,
|
DomainsFile,
|
||||||
@@ -870,6 +871,12 @@ class DashboardController:
|
|||||||
def dns_upstreams_save(self, cfg: DnsUpstreams) -> None:
|
def dns_upstreams_save(self, cfg: DnsUpstreams) -> None:
|
||||||
self.client.dns_upstreams_set(cfg)
|
self.client.dns_upstreams_set(cfg)
|
||||||
|
|
||||||
|
def dns_upstream_pool_view(self) -> DNSUpstreamPoolState:
|
||||||
|
return self.client.dns_upstream_pool_get()
|
||||||
|
|
||||||
|
def dns_upstream_pool_save(self, items: List[DNSBenchmarkUpstream]) -> DNSUpstreamPoolState:
|
||||||
|
return self.client.dns_upstream_pool_set(items)
|
||||||
|
|
||||||
def dns_benchmark(
|
def dns_benchmark(
|
||||||
self,
|
self,
|
||||||
upstreams: List[DNSBenchmarkUpstream],
|
upstreams: List[DNSBenchmarkUpstream],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from PySide6.QtWidgets import (
|
|||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from api_client import DNSBenchmarkUpstream, DnsUpstreams
|
from api_client import DNSBenchmarkUpstream
|
||||||
from dashboard_controller import DashboardController
|
from dashboard_controller import DashboardController
|
||||||
|
|
||||||
|
|
||||||
@@ -61,24 +61,22 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
self.ctrl = ctrl
|
self.ctrl = ctrl
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.refresh_cb = refresh_cb
|
self.refresh_cb = refresh_cb
|
||||||
self._last_recommended_default: List[str] = []
|
|
||||||
self._last_recommended_meta: List[str] = []
|
|
||||||
|
|
||||||
self.setWindowTitle("DNS benchmark")
|
self.setWindowTitle("DNS benchmark")
|
||||||
self.resize(980, 650)
|
self.resize(980, 660)
|
||||||
|
|
||||||
root = QVBoxLayout(self)
|
root = QVBoxLayout(self)
|
||||||
|
|
||||||
hint = QLabel(
|
hint = QLabel(
|
||||||
"List format: one DNS per row. Toggle checkbox to include in test. "
|
"One DNS per row. Checkbox means ACTIVE for resolver wave mode. "
|
||||||
"Then run benchmark and apply best DNS to resolver."
|
"Benchmark checks all rows and shows health."
|
||||||
)
|
)
|
||||||
hint.setWordWrap(True)
|
hint.setWordWrap(True)
|
||||||
hint.setStyleSheet("color: gray;")
|
hint.setStyleSheet("color: gray;")
|
||||||
root.addWidget(hint)
|
root.addWidget(hint)
|
||||||
|
|
||||||
self.tbl_sources = QTableWidget(0, 2)
|
self.tbl_sources = QTableWidget(0, 2)
|
||||||
self.tbl_sources.setHorizontalHeaderLabels(["Use", "DNS upstream"])
|
self.tbl_sources.setHorizontalHeaderLabels(["Active", "DNS upstream"])
|
||||||
self.tbl_sources.horizontalHeader().setStretchLastSection(True)
|
self.tbl_sources.horizontalHeader().setStretchLastSection(True)
|
||||||
self.tbl_sources.setSelectionBehavior(QTableWidget.SelectRows)
|
self.tbl_sources.setSelectionBehavior(QTableWidget.SelectRows)
|
||||||
self.tbl_sources.setSelectionMode(QTableWidget.SingleSelection)
|
self.tbl_sources.setSelectionMode(QTableWidget.SingleSelection)
|
||||||
@@ -95,6 +93,12 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
self.btn_reset = QPushButton("Reset defaults")
|
self.btn_reset = QPushButton("Reset defaults")
|
||||||
self.btn_reset.clicked.connect(self.on_reset_defaults)
|
self.btn_reset.clicked.connect(self.on_reset_defaults)
|
||||||
row_btns.addWidget(self.btn_reset)
|
row_btns.addWidget(self.btn_reset)
|
||||||
|
self.btn_reload = QPushButton("Reload active set")
|
||||||
|
self.btn_reload.clicked.connect(self.on_reload_pool)
|
||||||
|
row_btns.addWidget(self.btn_reload)
|
||||||
|
self.btn_save_pool = QPushButton("Save active set")
|
||||||
|
self.btn_save_pool.clicked.connect(self.on_save_pool)
|
||||||
|
row_btns.addWidget(self.btn_save_pool)
|
||||||
row_btns.addStretch(1)
|
row_btns.addStretch(1)
|
||||||
root.addLayout(row_btns)
|
root.addLayout(row_btns)
|
||||||
|
|
||||||
@@ -150,18 +154,12 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
self.tbl_results.setSelectionMode(QTableWidget.SingleSelection)
|
self.tbl_results.setSelectionMode(QTableWidget.SingleSelection)
|
||||||
root.addWidget(self.tbl_results, stretch=3)
|
root.addWidget(self.tbl_results, stretch=3)
|
||||||
|
|
||||||
apply_row = QHBoxLayout()
|
close_row = QHBoxLayout()
|
||||||
self.btn_apply_default = QPushButton("Apply top-2 to Default")
|
close_row.addStretch(1)
|
||||||
self.btn_apply_default.clicked.connect(self.on_apply_default)
|
|
||||||
apply_row.addWidget(self.btn_apply_default)
|
|
||||||
self.btn_apply_meta = QPushButton("Apply top-2 to Meta")
|
|
||||||
self.btn_apply_meta.clicked.connect(self.on_apply_meta)
|
|
||||||
apply_row.addWidget(self.btn_apply_meta)
|
|
||||||
apply_row.addStretch(1)
|
|
||||||
self.btn_close = QPushButton("Close")
|
self.btn_close = QPushButton("Close")
|
||||||
self.btn_close.clicked.connect(self.accept)
|
self.btn_close.clicked.connect(self.accept)
|
||||||
apply_row.addWidget(self.btn_close)
|
close_row.addWidget(self.btn_close)
|
||||||
root.addLayout(apply_row)
|
root.addLayout(close_row)
|
||||||
|
|
||||||
self._load_sources()
|
self._load_sources()
|
||||||
self._load_domains()
|
self._load_domains()
|
||||||
@@ -173,33 +171,51 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
QMessageBox.critical(self, title, str(e))
|
QMessageBox.critical(self, title, str(e))
|
||||||
|
|
||||||
def _load_sources(self) -> None:
|
def _load_sources(self) -> None:
|
||||||
raw = str(self.settings.value("dns_benchmark/upstreams", "") or "").strip()
|
|
||||||
rows: List[tuple[bool, str]] = []
|
rows: List[tuple[bool, str]] = []
|
||||||
if raw:
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(raw)
|
st = self.ctrl.dns_upstream_pool_view()
|
||||||
if isinstance(data, list):
|
for item in st.items:
|
||||||
for item in data:
|
addr = str(item.addr or "").strip()
|
||||||
if not isinstance(item, dict):
|
if not addr:
|
||||||
continue
|
continue
|
||||||
addr = str(item.get("addr") or "").strip()
|
rows.append((bool(item.enabled), addr))
|
||||||
if not addr:
|
except Exception:
|
||||||
continue
|
rows = []
|
||||||
rows.append((bool(item.get("enabled", True)), addr))
|
|
||||||
except Exception:
|
if not rows:
|
||||||
rows = []
|
raw = str(self.settings.value("dns_benchmark/upstreams", "") or "").strip()
|
||||||
|
if raw:
|
||||||
|
try:
|
||||||
|
data = json.loads(raw)
|
||||||
|
if isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
addr = str(item.get("addr") or "").strip()
|
||||||
|
if not addr:
|
||||||
|
continue
|
||||||
|
rows.append((bool(item.get("enabled", True)), addr))
|
||||||
|
except Exception:
|
||||||
|
rows = []
|
||||||
|
|
||||||
if not rows:
|
if not rows:
|
||||||
rows = [(True, item) for item in DEFAULT_UPSTREAMS]
|
rows = [(True, item) for item in DEFAULT_UPSTREAMS]
|
||||||
|
|
||||||
|
self.tbl_sources.blockSignals(True)
|
||||||
self.tbl_sources.setRowCount(0)
|
self.tbl_sources.setRowCount(0)
|
||||||
for enabled, addr in rows:
|
for enabled, addr in rows:
|
||||||
self._append_source_row(enabled, addr)
|
self._append_source_row(enabled, addr)
|
||||||
|
self.tbl_sources.blockSignals(False)
|
||||||
|
self._save_settings()
|
||||||
|
|
||||||
def _load_domains(self) -> None:
|
def _load_domains(self) -> None:
|
||||||
raw = str(self.settings.value("dns_benchmark/domains", "") or "").strip()
|
raw = str(self.settings.value("dns_benchmark/domains", "") or "").strip()
|
||||||
if not raw:
|
if not raw:
|
||||||
raw = "\n".join(DEFAULT_DOMAINS)
|
raw = "\n".join(DEFAULT_DOMAINS)
|
||||||
|
self.txt_domains.blockSignals(True)
|
||||||
self.txt_domains.setPlainText(raw)
|
self.txt_domains.setPlainText(raw)
|
||||||
|
self.txt_domains.blockSignals(False)
|
||||||
|
|
||||||
def _save_settings(self) -> None:
|
def _save_settings(self) -> None:
|
||||||
items = []
|
items = []
|
||||||
@@ -264,14 +280,33 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
row = self.tbl_sources.currentRow()
|
row = self.tbl_sources.currentRow()
|
||||||
if row >= 0:
|
if row >= 0:
|
||||||
self.tbl_sources.removeRow(row)
|
self.tbl_sources.removeRow(row)
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
|
|
||||||
def on_reset_defaults(self) -> None:
|
def on_reset_defaults(self) -> None:
|
||||||
|
self.tbl_sources.blockSignals(True)
|
||||||
self.tbl_sources.setRowCount(0)
|
self.tbl_sources.setRowCount(0)
|
||||||
for item in DEFAULT_UPSTREAMS:
|
for item in DEFAULT_UPSTREAMS:
|
||||||
self._append_source_row(True, item)
|
self._append_source_row(True, item)
|
||||||
|
self.tbl_sources.blockSignals(False)
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
|
|
||||||
|
def on_reload_pool(self) -> None:
|
||||||
|
self._safe(self._load_sources, "Reload DNS active set error")
|
||||||
|
|
||||||
|
def on_save_pool(self) -> None:
|
||||||
|
def work() -> None:
|
||||||
|
payload = self._source_payload()
|
||||||
|
st = self.ctrl.dns_upstream_pool_save(payload)
|
||||||
|
active = sum(1 for x in st.items if x.enabled)
|
||||||
|
total = len(st.items)
|
||||||
|
self._save_settings()
|
||||||
|
self.lbl_summary.setText(f"Saved active DNS set: active={active}/{total}")
|
||||||
|
self.lbl_summary.setStyleSheet("color: green;")
|
||||||
|
if self.refresh_cb:
|
||||||
|
self.refresh_cb()
|
||||||
|
|
||||||
|
self._safe(work, "Save DNS active set error")
|
||||||
|
|
||||||
def on_run_benchmark(self) -> None:
|
def on_run_benchmark(self) -> None:
|
||||||
def work() -> None:
|
def work() -> None:
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
@@ -284,8 +319,6 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
attempts=int(self.spin_attempts.value()),
|
attempts=int(self.spin_attempts.value()),
|
||||||
concurrency=int(self.spin_concurrency.value()),
|
concurrency=int(self.spin_concurrency.value()),
|
||||||
)
|
)
|
||||||
self._last_recommended_default = list(resp.recommended_default or [])
|
|
||||||
self._last_recommended_meta = list(resp.recommended_meta or [])
|
|
||||||
self._render_results(resp)
|
self._render_results(resp)
|
||||||
if self.refresh_cb:
|
if self.refresh_cb:
|
||||||
self.refresh_cb()
|
self.refresh_cb()
|
||||||
@@ -320,11 +353,9 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
st_item.setForeground(QColor("red"))
|
st_item.setForeground(QColor("red"))
|
||||||
self.tbl_results.setItem(row, 6, st_item)
|
self.tbl_results.setItem(row, 6, st_item)
|
||||||
|
|
||||||
dflt = ", ".join(resp.recommended_default or []) or "—"
|
|
||||||
meta = ", ".join(resp.recommended_meta or []) or "—"
|
|
||||||
self.lbl_summary.setText(
|
self.lbl_summary.setText(
|
||||||
f"Checked: {len(resp.results)} DNS | domains={len(resp.domains_used)} "
|
f"Checked: {len(resp.results)} DNS | domains={len(resp.domains_used)} "
|
||||||
f"| timeout={resp.timeout_ms}ms | rec default: {dflt} | rec meta: {meta}"
|
f"| timeout={resp.timeout_ms}ms"
|
||||||
)
|
)
|
||||||
self.lbl_summary.setStyleSheet("color: gray;")
|
self.lbl_summary.setStyleSheet("color: gray;")
|
||||||
|
|
||||||
@@ -334,43 +365,3 @@ class DNSBenchmarkDialog(QDialog):
|
|||||||
self.settings.setValue("dns_benchmark/last_ok", ok_total)
|
self.settings.setValue("dns_benchmark/last_ok", ok_total)
|
||||||
self.settings.setValue("dns_benchmark/last_fail", fail_total)
|
self.settings.setValue("dns_benchmark/last_fail", fail_total)
|
||||||
self.settings.setValue("dns_benchmark/last_timeout", timeout_total)
|
self.settings.setValue("dns_benchmark/last_timeout", timeout_total)
|
||||||
|
|
||||||
def on_apply_default(self) -> None:
|
|
||||||
def work() -> None:
|
|
||||||
picks = list(self._last_recommended_default or [])
|
|
||||||
if len(picks) < 2:
|
|
||||||
raise ValueError("run benchmark first (need at least 2 recommended DNS)")
|
|
||||||
cur = self.ctrl.dns_upstreams_view()
|
|
||||||
cfg = DnsUpstreams(
|
|
||||||
default1=picks[0],
|
|
||||||
default2=picks[1],
|
|
||||||
meta1=cur.meta1,
|
|
||||||
meta2=cur.meta2,
|
|
||||||
)
|
|
||||||
self.ctrl.dns_upstreams_save(cfg)
|
|
||||||
if self.refresh_cb:
|
|
||||||
self.refresh_cb()
|
|
||||||
self.lbl_summary.setText(f"Applied default DNS: {picks[0]}, {picks[1]}")
|
|
||||||
self.lbl_summary.setStyleSheet("color: green;")
|
|
||||||
|
|
||||||
self._safe(work, "Apply default DNS error")
|
|
||||||
|
|
||||||
def on_apply_meta(self) -> None:
|
|
||||||
def work() -> None:
|
|
||||||
picks = list(self._last_recommended_meta or [])
|
|
||||||
if len(picks) < 2:
|
|
||||||
raise ValueError("run benchmark first (need at least 2 recommended DNS)")
|
|
||||||
cur = self.ctrl.dns_upstreams_view()
|
|
||||||
cfg = DnsUpstreams(
|
|
||||||
default1=cur.default1,
|
|
||||||
default2=cur.default2,
|
|
||||||
meta1=picks[0],
|
|
||||||
meta2=picks[1],
|
|
||||||
)
|
|
||||||
self.ctrl.dns_upstreams_save(cfg)
|
|
||||||
if self.refresh_cb:
|
|
||||||
self.refresh_cb()
|
|
||||||
self.lbl_summary.setText(f"Applied meta DNS: {picks[0]}, {picks[1]}")
|
|
||||||
self.lbl_summary.setStyleSheet("color: green;")
|
|
||||||
|
|
||||||
self._safe(work, "Apply meta DNS error")
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from PySide6.QtWidgets import (
|
|||||||
QProgressBar,
|
QProgressBar,
|
||||||
)
|
)
|
||||||
|
|
||||||
from api_client import ApiClient, DnsUpstreams
|
from api_client import ApiClient
|
||||||
from dashboard_controller import DashboardController, TraceMode
|
from dashboard_controller import DashboardController, TraceMode
|
||||||
from dns_benchmark_dialog import DNSBenchmarkDialog
|
from dns_benchmark_dialog import DNSBenchmarkDialog
|
||||||
from traffic_mode_dialog import TrafficModeDialog
|
from traffic_mode_dialog import TrafficModeDialog
|
||||||
@@ -729,14 +729,24 @@ RU: Источник wildcard IP: резолвер, runtime nftset SmartDNS, и
|
|||||||
self.lbl_dns_mode_state.setText(txt)
|
self.lbl_dns_mode_state.setText(txt)
|
||||||
self.lbl_dns_mode_state.setStyleSheet(f"color: {color};")
|
self.lbl_dns_mode_state.setStyleSheet(f"color: {color};")
|
||||||
|
|
||||||
def _set_dns_resolver_summary(self, ups: DnsUpstreams) -> None:
|
def _set_dns_resolver_summary(self, pool_items) -> None:
|
||||||
d1 = (ups.default1 or "—").strip() or "—"
|
active = []
|
||||||
d2 = (ups.default2 or "—").strip() or "—"
|
total = 0
|
||||||
m1 = (ups.meta1 or "—").strip() or "—"
|
for item in pool_items or []:
|
||||||
m2 = (ups.meta2 or "—").strip() or "—"
|
addr = str(getattr(item, "addr", "") or "").strip()
|
||||||
self.lbl_dns_resolver_upstreams.setText(
|
if not addr:
|
||||||
f"Resolver upstreams: default[{d1}, {d2}] meta[{m1}, {m2}]"
|
continue
|
||||||
)
|
total += 1
|
||||||
|
if bool(getattr(item, "enabled", False)):
|
||||||
|
active.append(addr)
|
||||||
|
if not active:
|
||||||
|
text = f"Resolver upstreams: active=0/{total} (empty set)"
|
||||||
|
else:
|
||||||
|
preview = ", ".join(active[:4])
|
||||||
|
if len(active) > 4:
|
||||||
|
preview += f", +{len(active)-4} more"
|
||||||
|
text = f"Resolver upstreams: active={len(active)}/{total} [{preview}]"
|
||||||
|
self.lbl_dns_resolver_upstreams.setText(text)
|
||||||
self.lbl_dns_resolver_upstreams.setStyleSheet("color: gray;")
|
self.lbl_dns_resolver_upstreams.setStyleSheet("color: gray;")
|
||||||
|
|
||||||
avg_ms = self._ui_settings.value("dns_benchmark/last_avg_ms", None)
|
avg_ms = self._ui_settings.value("dns_benchmark/last_avg_ms", None)
|
||||||
@@ -1072,8 +1082,8 @@ RU: Источник wildcard IP: резолвер, runtime nftset SmartDNS, и
|
|||||||
def work():
|
def work():
|
||||||
self._dns_ui_refresh = True
|
self._dns_ui_refresh = True
|
||||||
try:
|
try:
|
||||||
ups = self.ctrl.dns_upstreams_view()
|
pool = self.ctrl.dns_upstream_pool_view()
|
||||||
self._set_dns_resolver_summary(ups)
|
self._set_dns_resolver_summary(getattr(pool, "items", []))
|
||||||
|
|
||||||
st = self.ctrl.dns_status_view()
|
st = self.ctrl.dns_status_view()
|
||||||
self.ent_smartdns_addr.setText(st.smartdns_addr or "")
|
self.ent_smartdns_addr.setText(st.smartdns_addr or "")
|
||||||
|
|||||||
Reference in New Issue
Block a user