baseline: api+gui traffic mode + candidates picker
Snapshot before app-launcher (cgroup/mark) work; ignore binaries/backups.
This commit is contained in:
565
selective-vpn-api/инструкция2.txt
Normal file
565
selective-vpn-api/инструкция2.txt
Normal file
@@ -0,0 +1,565 @@
|
||||
Ок, давай разберём по двум пунктам:
|
||||
|
||||
Финальные имена и что именно править в коде
|
||||
|
||||
Почему стало ~590 IP вместо привычных ~1700
|
||||
|
||||
1. Имена / где что менять
|
||||
|
||||
Чтобы не было зоопарка, фиксируем одну схему:
|
||||
|
||||
внутренний конфиг резолвера:
|
||||
|
||||
// types.go
|
||||
type DNSMode string
|
||||
|
||||
const (
|
||||
DNSModeDirect DNSMode = "direct" // ходим напрямую на default/meta
|
||||
DNSModeSmartOnly DNSMode = "smartdns" // всё через SmartDNS
|
||||
)
|
||||
|
||||
type dnsConfig struct {
|
||||
Default []string `json:"default"`
|
||||
Meta []string `json:"meta"`
|
||||
SmartDNS string `json:"smartdns"`
|
||||
Mode DNSMode `json:"mode"`
|
||||
}
|
||||
|
||||
|
||||
Важно: одно имя — dnsConfig, одно поле режима — Mode.
|
||||
Никаких ViaSmartDNS, ViaSmartDnS и т.п. – только Mode.
|
||||
|
||||
1.1. loadDNSConfig (используется резолвером)
|
||||
|
||||
Внутри dns_settings.go (или там, где у тебя сейчас чтение /etc/selective-vpn/dns-upstreams.conf) логика должна отдавать именно такой dnsConfig:
|
||||
|
||||
func loadDNSConfig(path string, logf func(string, ...any)) dnsConfig {
|
||||
cfg := dnsConfig{
|
||||
Default: []string{defaultDNS1, defaultDNS2},
|
||||
Meta: []string{defaultMeta1, defaultMeta2},
|
||||
SmartDNS: "",
|
||||
Mode: DNSModeDirect,
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
// файла нет – просто дефолты
|
||||
return cfg
|
||||
}
|
||||
|
||||
var def, meta []string
|
||||
mode := DNSModeDirect
|
||||
smart := ""
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, ln := range lines {
|
||||
s := strings.TrimSpace(ln)
|
||||
if s == "" || strings.HasPrefix(s, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(s)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(parts[0])
|
||||
vals := parts[1:]
|
||||
|
||||
switch key {
|
||||
case "default":
|
||||
def = append(def, vals...)
|
||||
case "meta":
|
||||
meta = append(meta, vals...)
|
||||
case "smartdns":
|
||||
if len(vals) > 0 {
|
||||
smart = vals[0]
|
||||
}
|
||||
case "mode":
|
||||
switch DNSMode(vals[0]) {
|
||||
case DNSModeSmartOnly:
|
||||
mode = DNSModeSmartOnly
|
||||
default:
|
||||
mode = DNSModeDirect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(def) > 0 {
|
||||
cfg.Default = def
|
||||
}
|
||||
if len(meta) > 0 {
|
||||
cfg.Meta = meta
|
||||
}
|
||||
if smart != "" {
|
||||
cfg.SmartDNS = smart
|
||||
}
|
||||
cfg.Mode = mode
|
||||
|
||||
if logf != nil {
|
||||
if cfg.Mode == DNSModeSmartOnly && cfg.SmartDNS != "" {
|
||||
logf("dns-config: mode=smartdns smartdns=%s", cfg.SmartDNS)
|
||||
} else {
|
||||
logf("dns-config: mode=direct default=%v meta=%v", cfg.Default, cfg.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
1.2. runResolverJob — как он выбирает режим
|
||||
|
||||
Твой кусок:
|
||||
|
||||
cfg := loadDNSConfig(opts.DNSConfigPath, logf)
|
||||
if logf != nil {
|
||||
if cfg.ViaSmartDNS {
|
||||
logf("resolver dns mode: SmartDNS-only (%v)", cfg.Default)
|
||||
} else {
|
||||
logf("resolver dns mode: direct default=%v meta=%v", cfg.Default, cfg.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Нужно заменить на:
|
||||
|
||||
cfg := loadDNSConfig(opts.DNSConfigPath, logf)
|
||||
|
||||
if logf != nil {
|
||||
if cfg.Mode == DNSModeSmartOnly && cfg.SmartDNS != "" {
|
||||
logf("resolver dns mode: SmartDNS-only (%s)", cfg.SmartDNS)
|
||||
} else {
|
||||
logf("resolver dns mode: direct default=%v meta=%v", cfg.Default, cfg.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
1.3. Как резолвер реально ходит в DNS
|
||||
|
||||
В resolveHostGo сейчас у тебя примерно так (я по смыслу):
|
||||
|
||||
func resolveHostGo(host string, cfg dnsConfig, metaSpecial []string, logf func(string, ...any)) ([]string, int) {
|
||||
useMeta := false
|
||||
for _, m := range metaSpecial {
|
||||
if host == m {
|
||||
useMeta = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dnsList := cfg.Default
|
||||
if useMeta {
|
||||
dnsList = cfg.Meta
|
||||
}
|
||||
|
||||
ips, errs := digA(host, dnsList, 3*time.Second, logf)
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
Делаем так, чтобы при включённом SmartDNS всегда шли только на него:
|
||||
|
||||
func resolveHostGo(host string, cfg dnsConfig, metaSpecial []string, logf func(string, ...any)) ([]string, int) {
|
||||
useMeta := false
|
||||
for _, m := range metaSpecial {
|
||||
if host == m {
|
||||
useMeta = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var dnsList []string
|
||||
|
||||
if cfg.Mode == DNSModeSmartOnly && cfg.SmartDNS != "" {
|
||||
// ВСЁ через SmartDNS
|
||||
dnsList = []string{cfg.SmartDNS}
|
||||
} else if useMeta {
|
||||
dnsList = cfg.Meta
|
||||
} else {
|
||||
dnsList = cfg.Default
|
||||
}
|
||||
|
||||
ips, errs := digA(host, dnsList, 3*time.Second, logf)
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
И для PTR-запросов (resolveStaticLabels → digPTR) — аналогично:
|
||||
|
||||
func resolveStaticLabels(entries [][3]string, cfg dnsConfig, ptrCache map[string]any, ttl int, logf func(string, ...any)) (map[string][]string, int, int) {
|
||||
...
|
||||
dnsForPtr := ""
|
||||
|
||||
if cfg.Mode == DNSModeSmartOnly && cfg.SmartDNS != "" {
|
||||
dnsForPtr = cfg.SmartDNS
|
||||
} else if len(cfg.Default) > 0 {
|
||||
dnsForPtr = cfg.Default[0]
|
||||
} else {
|
||||
dnsForPtr = defaultDNS1
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
1.4. routes_update.go — что именно должно быть
|
||||
|
||||
Там, где запускается резолвер (что-то вроде runResolverJob), должно быть только это (без своих конфигов):
|
||||
|
||||
opts := ResolverOpts{
|
||||
DomainsPath: domainDir + "/bases.txt",
|
||||
MetaPath: domainDir + "/meta-special.txt",
|
||||
StaticPath: staticIPsFile,
|
||||
CachePath: stateDir + "/resolver-cache.json",
|
||||
PtrCachePath: stateDir + "/resolver-ptr-cache.json",
|
||||
TraceLog: traceLogPath,
|
||||
TTL: 24 * 3600, // или твой config.ResolverTTLSeconds
|
||||
Workers: 200, // или config.ResolverWorkers
|
||||
DNSConfigPath: dnsUpstreamsConf, // ВАЖНО: один путь, один формат
|
||||
}
|
||||
|
||||
res, err := runResolverJob(opts, logf)
|
||||
|
||||
|
||||
Никаких других структур / полей для DNS в этом файле не нужно.
|
||||
Вся магия DNS живёт в dns_settings.go + resolver.go.
|
||||
|
||||
2. Почему теперь 590 IP, а не 1700
|
||||
|
||||
Смотрим на лог:
|
||||
|
||||
summary: domains=14864, cache_hits=2 resolved_now=1281 unresolved=13581 static_entries=294 ...
|
||||
unique_ips=590 ...
|
||||
|
||||
|
||||
Это говорит о трёх вещах:
|
||||
|
||||
Резолвер реально прошёлся по всем ~15k доменам.
|
||||
|
||||
Ответы он получил только по ~1281 домену.
|
||||
|
||||
Из них после фильтра isPrivateIPv4 осталось 590 уникальных публичных IP.
|
||||
|
||||
Что поменялось по сравнению с прошлой версией:
|
||||
|
||||
2.1. Всё теперь идёт ТОЛЬКО через SmartDNS
|
||||
|
||||
Раньше схема была примерно:
|
||||
|
||||
резолвер → напрямую на 94.140.* + 46.243.*
|
||||
|
||||
SmartDNS жил своей жизнью как локальный помощник
|
||||
|
||||
Сейчас при Use SmartDNS for resolver:
|
||||
|
||||
резолвер → только на 127.0.0.1#6053
|
||||
|
||||
SmartDNS → дальше сам ходит на 192.168.50.10:53 (bind) → AdGuard DNS и т.д.
|
||||
|
||||
Поэтому:
|
||||
|
||||
все блокировки / NXDOMAIN / подмена на 0.0.0.0 / 127.0.0.1 происходят до нашего Go-кода;
|
||||
|
||||
Go-резолвер видит либо нормальный внешний IP, либо 0.0.0.0/локалку/ошибку.
|
||||
|
||||
2.2. Мы фильтруем все приватные и мусорные IP
|
||||
|
||||
В runResolverJob:
|
||||
|
||||
if len(rawIps) > 0 && ts > 0 && now-int(ts) <= ttl {
|
||||
for _, v := range rawIps {
|
||||
if s, ok := v.(string); ok && s != "" && !isPrivateIPv4(s) {
|
||||
ips = append(ips, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
if isPrivateIPv4(ip) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
А isPrivateIPv4 считает приватными:
|
||||
|
||||
0.x.x.x
|
||||
|
||||
10.x.x.x
|
||||
|
||||
127.x.x.x
|
||||
|
||||
192.168.x.x
|
||||
|
||||
172.16–31.x.x
|
||||
|
||||
и вообще всё, что невалидное
|
||||
|
||||
То есть:
|
||||
|
||||
если AdGuard/SmartDNS возвращает 0.0.0.0 или 127.0.0.1 для трекеров / рекламы,
|
||||
мы их больше не учитываем вообще;
|
||||
|
||||
раньше, когда ходили мимо SmartDNS, таких «мусорных» ответов было гораздо меньше.
|
||||
|
||||
Отсюда очень понятная картина:
|
||||
|
||||
1700 IP ранее = реальные IP + всякий мусор (0.0.0.0, локалки, возможно ещё что-то);
|
||||
|
||||
590 IP сейчас = только реальные публичные адреса, которые реально нужно засунуть в ipset и гонять через VPN.
|
||||
|
||||
2.3. Что с ошибками в логах вида ... on 192.168.50.10:53
|
||||
|
||||
Это просто текст ошибки из SmartDNS/bind, который пробрасывается наружу:
|
||||
|
||||
ptr error 104.26.10.38 via 127.0.0.1:6053: lookup 38.10.26.104.in-addr.arpa. on 192.168.50.10:53: no such host
|
||||
|
||||
|
||||
мы обращаемся к 127.0.0.1:6053;
|
||||
|
||||
SmartDNS дальше ходит на 192.168.50.10:53 и говорит «no such host»;
|
||||
|
||||
net.Resolver формирует такое красивое сообщение.
|
||||
|
||||
То есть Go-код SmartDNS не обходит, всё как раз правильно идёт через него.
|
||||
|
||||
Если хочешь «как раньше» по цифрам
|
||||
|
||||
Если прям хочется снова видеть ~1700 IP (включая мусор):
|
||||
|
||||
Можно временно ослабить фильтр:
|
||||
|
||||
func isPrivateIPv4(ip string) bool {
|
||||
// В тесте можно не резать 0.0.0.0 / 127.x.x.x и т.п.,
|
||||
// просто вернуть false и посмотреть цифры
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Или сделать две метрики:
|
||||
|
||||
unique_ips_total — с учётом всего, что вернул DNS;
|
||||
|
||||
unique_ips_public — после фильтра приватных;
|
||||
|
||||
и логировать обе.
|
||||
|
||||
Но с точки зрения селективных маршрутов тебе как раз нужны только публичные IP, так что текущие 590 очень вероятно ближе к «правильной» картине.
|
||||
|
||||
Если хочешь, в следующем шаге могу:
|
||||
|
||||
дать патч для логов, чтобы в summary было:
|
||||
unique_ips_public, unique_ips_private, blocked_0_ips и т.д.,
|
||||
|
||||
плюс подсветить в Trace вкладке отдельной строкой, сколько доменов SmartDNS убил в ноль.
|
||||
|
||||
в том то и дело, что теперь половина доменов которые железно работали, не работают теперь, что то изменилось, не понимаю что, у нас кеш пишется? что же не так ... и кстати логи по прежнему не верно отображается
|
||||
Перестала думать
|
||||
в том то и дело, что теперь половина доменов которые железно работали, не работают теперь, что то изменилось, не понимаю что, у нас кеш пишется? что же не так ... и кстати логи по прежнему не верно отображается
|
||||
Думал на протяжении 2m 20s
|
||||
|
||||
Окей, давай по порядку, что видно из скрина логов, и что мы реально поменяли в коде.
|
||||
|
||||
⚠️ Сначала техническая ремарка
|
||||
Часть файлов, которые ты раньше заливал (resolver.go, config.go и т.д.), у меня уже недоступны – система их подчищает со временем. Я помню общую структуру и последние куски, которые ты присылал в текст, но если захочешь, чтобы я дал прям точные патчи построчно по актуальному resolver.go / dns_settings.go, лучше закинуть их ещё раз.
|
||||
|
||||
1. Что сейчас не так по логам
|
||||
|
||||
На твоём скрине снизу была строчка типа:
|
||||
|
||||
dns_errors=13295 unresolved=13581 unique_ips=590 ...
|
||||
|
||||
|
||||
И вверху куча:
|
||||
|
||||
dns warn www.twitterinc.com via 127.0.0.1:6053:
|
||||
lookup www.twitterinc.com on 192.168.50.53:53: i/o timeout
|
||||
...
|
||||
dns warn www.twitterstat.us via 127.0.0.1:6053:
|
||||
dial udp 127.0.0.1:6053: i/o timeout
|
||||
|
||||
|
||||
Это говорит о простом:
|
||||
|
||||
Домены в большинстве случаев не резолвятся вообще.
|
||||
Не из-за кэша, а из-за того, что SmartDNS/BIND/AdGuard под нагрузкой просто не отвечает (таймауты).
|
||||
|
||||
Из-за этого у нас:
|
||||
|
||||
resolved_now маленький
|
||||
|
||||
dns_errors огромный
|
||||
|
||||
unique_ips ≈ 500+, хотя раньше было ≈ 1700.
|
||||
|
||||
То есть лог честный: с точки зрения нашего Go-резолвера для этих доменов реально "no IPs".
|
||||
|
||||
2. Что именно изменилось в логике DNS
|
||||
|
||||
До рефакторинга:
|
||||
|
||||
наш resolver.go бился напрямую в default1/default2/meta1/meta2 (типа 94.140.14.15 и т.д.);
|
||||
|
||||
SmartDNS жил сам по себе и занимался вайлдкарт-ipset’ами.
|
||||
|
||||
После рефакторинга (который мы как раз и делали по твоей просьбе):
|
||||
|
||||
если отмечено «Use SmartDNS for resolver» — мы:
|
||||
|
||||
подменяем cfg.Default = []string{ smartdnsAddr } (127.0.0.1#6053);
|
||||
|
||||
и всё резолвим через SmartDNS;
|
||||
|
||||
параллельно мы оставили очень агрессивный пул: workers = 200, domains ≈ 15k.
|
||||
|
||||
И получается ситуация:
|
||||
|
||||
15 000 доменов × 200 воркеров × таймаут 3 секунды → местный SmartDNS + BIND просто не успевает, начинает дропать/таймаутить, мы видим кучу i/o timeout, и в итоге получаем только ~590 IP.
|
||||
|
||||
То есть сломали не кэш, а заддосили свой же локальный resolver 😅
|
||||
|
||||
3. Кэш – жив он или нет?
|
||||
|
||||
Да, по коду (который ты кидал):
|
||||
|
||||
domainCache := loadJSONMap(opts.CachePath)
|
||||
ptrCache := loadJSONMap(opts.PtrCachePath)
|
||||
...
|
||||
domainCache[host] = map[string]any{
|
||||
"ips": r.ips,
|
||||
"last_resolved": now,
|
||||
}
|
||||
...
|
||||
res.DomainCache = domainCache
|
||||
res.PtrCache = ptrCache
|
||||
...
|
||||
saveJSON(res.DomainCache, opts.CachePath)
|
||||
saveJSON(res.PtrCache, opts.PtrCachePath)
|
||||
|
||||
|
||||
Кэш:
|
||||
|
||||
читается в начале (loadJSONMap);
|
||||
|
||||
обновляется только для успешных доменов;
|
||||
|
||||
сохраняется в конце.
|
||||
|
||||
Проблема в другом: когда у тебя за один прогон 13k DNS-ошибок, в кэш почти нечего писать — большая часть доменов вообще не получила IP, и в следующий прогон они снова идут "с нуля".
|
||||
|
||||
4. Что я бы сделал сейчас в коде (минимально и по делу)
|
||||
4.1. Ограничить параллелизм при работе через SmartDNS
|
||||
|
||||
В runResolverJob (там, где ты сейчас считаешь workers), сделай так:
|
||||
|
||||
workers := opts.Workers
|
||||
if workers <= 0 {
|
||||
workers = 200
|
||||
}
|
||||
|
||||
// если работаем через SmartDNS – не лупим по нему 200 горутин
|
||||
if cfg.ViaSmartDNS && workers > 50 {
|
||||
workers = 50
|
||||
}
|
||||
|
||||
// safety clamps
|
||||
if workers < 1 {
|
||||
workers = 1
|
||||
}
|
||||
if workers > 500 {
|
||||
workers = 500
|
||||
}
|
||||
|
||||
if logf != nil {
|
||||
logf("resolver start: domains=%d ttl=%ds workers=%d viaSmartDNS=%v",
|
||||
len(domains), ttl, workers, cfg.ViaSmartDNS)
|
||||
}
|
||||
|
||||
|
||||
Суть:
|
||||
|
||||
при прямых DNS — всё как раньше (200 воркеров нормально жрут 94.140.*);
|
||||
|
||||
при SmartDNS — ставим жёсткий потолок, например 50 (можно 30-80 поэкспериментировать).
|
||||
|
||||
Это радикально уменьшит количество одновременных запросов к SmartDNS/BIND, и таймаутов станет сильно меньше → dns_errors упадут, unique_ips вырастут.
|
||||
|
||||
4.2. (Опционально) Увеличить таймаут для SmartDNS
|
||||
|
||||
Сейчас digA вызывается так (по прошлому коду):
|
||||
|
||||
ips, errs := digA(host, dnsList, 3*time.Second, logf)
|
||||
|
||||
|
||||
Если хочешь мягко подстраховаться:
|
||||
|
||||
timeout := 3 * time.Second
|
||||
if cfg.ViaSmartDNS {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
ips, errs := digA(host, dnsList, timeout, logf)
|
||||
|
||||
|
||||
Для этого нужно просто протащить cfg или флажок viaSmartDNS до resolveHostGo:
|
||||
|
||||
func resolveHostGo(host string, cfg dnsConfig, metaSpecial []string, viaSmartDNS bool, logf func(string, ...any)) ([]string, int) {
|
||||
...
|
||||
timeout := 3 * time.Second
|
||||
if viaSmartDNS {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
ips, errs := digA(host, dnsList, timeout, logf)
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
И в runResolverJob вызывать:
|
||||
|
||||
ips, errs := resolveHostGo(j.host, cfg, metaSpecial, cfg.ViaSmartDNS, logf)
|
||||
|
||||
|
||||
Это не обязательно, но в связке с уменьшением workers даст более стабильную картину.
|
||||
|
||||
5. Почему лог «неверно отображается»
|
||||
|
||||
Тут важно разделить:
|
||||
|
||||
То, что реально делает наш резолвер.
|
||||
Если он пишет:
|
||||
|
||||
dns warn www.twitterinc.com via 127.0.0.1:6053: dial udp 127.0.0.1:6053: i/o timeout
|
||||
|
||||
|
||||
– это значит, что конкретно этот запрос реально упал по таймауту.
|
||||
Даже если "в браузере всё открывается", это уже другие запросы, в другое время и с другим паттерном (не 15k доменов за раз).
|
||||
|
||||
Фильтрация на вкладке Events/SmartDNS/Full.
|
||||
Мы уже делали фильтр для mode=gui, чтобы убрать:
|
||||
|
||||
[routes], resolver, dnstt, smartdns, nft add element, cache hit и т.п. шум.
|
||||
|
||||
Если сейчас в режиме Events ты всё равно видишь этот трэш с [routes] dns warn ... — значит, в handleTraceJSON в ветке case "gui": надо ещё сильнее ужать фильтр, например:
|
||||
|
||||
if strings.HasPrefix(ll, "[routes]") ||
|
||||
strings.Contains(ll, " dns warn ") ||
|
||||
strings.Contains(ll, " dns error ") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Тогда:
|
||||
|
||||
весь подробный DNS-дебаг остаётся в Full и SmartDNS;
|
||||
|
||||
Events показывает только человеческие штуки [gui], [login], [vpn], [error] и т.д.
|
||||
|
||||
Если хочешь, чтобы я точно подогнал фильтр под твой текущий trace_handlers.go, просто перекинь файл ещё раз — старая версия у меня уже протухла.
|
||||
|
||||
6. Что бы я сделал дальше по шагам
|
||||
|
||||
Внести правку с ограничением workers при ViaSmartDNS.
|
||||
|
||||
(Опционально) Протянуть флаг viaSmartDNS в resolveHostGo и увеличить таймаут до 5s.
|
||||
|
||||
Запустить update-selective-routes и посмотреть:
|
||||
|
||||
строку summary (особенно dns_errors и unique_ips);
|
||||
|
||||
стало ли больше IP (должно быть ближе к твоим старым ~1700, хотя не обязательно 1:1).
|
||||
|
||||
Если с IP всё норм, но Events всё ещё замусорен – подправить фильтр в handleTraceJSON (mode = "gui").
|
||||
|
||||
Если хочешь, чтобы я прям по твоим актуальным файлам выписал патчи diffом (resolver.go, dns_settings.go, trace_handlers.go) – скинь их ещё раз, я аккуратно вставлю всё в нужные места без переименования функций и с теми же структурами, что уже есть.
|
||||
Reference in New Issue
Block a user