Анализ твоего кода resolver.go Я внимательно прошёлся по всему коду: структура хорошая (модульный, с логами, кэшем, concurrent резолвом), логика правильная для задачи (домены → IPs с meta/default upstream'ами, static с PTR-лейблами, приватные IP фильтруем). Это уже рабочий инструмент, который может обрабатывать тысячи доменов без сбоев. Но да, есть потенциальные проблемы и места для улучшений — некоторые очевидные (edge-кейсы), другие — для масштаба/надёжности. Я разделил на потенциальные поломки (где код может крашнуться или дать неверный результат), слабые места (не поломки, но риски в проде) и рекомендации с кодовыми примерами. Всё основано на твоём коде — не выдумываю, а анализирую. 1. Потенциальные поломки (где код может упасть или дать мусор) Конфликт портов в upstream (в resolveHostGo и digPTR): splitDNS возвращает port (e.g., "127.0.0.1#6053" → "127.0.0.1", "6053"). Но в net.Resolver.Dial ты используешь net.JoinHostPort(server, port) — это ок, но Resolver по дефолту использует UDP. Если upstream требует TCP (e.g., большие ответы >512 байт) — запрос fail (dns.exception.SyntaxError или timeout). Поломка: Для доменов с большим TXT/NS — no IPs, но errs++ (не обновит кэш). Тест: Резолв домен с большим ответом (e.g., "example.com" TXT) на upstream с port. Если fail — вот оно. Timeout слишком короткий (3s в ctx): На медленных upstream'ах (e.g., DNS over Tor или VPN) — запрос не успеет, вернёт [] и errs++. Поломка: Unresolved домены, даже если upstream живой. При повторном запуске — из кэша не возьмёт (потому что не обновил). Тест: Установи медленный DNS (e.g., 8.8.8.8 с delay) — увидишь dns.timeout. PTR fail без retry (в resolveStaticLabels): digPTR без retry — если upstream временно down, PTR не получит, и лейбл = "*[STATIC-IP]". Поломка: Если все upstream fail — все static без PTR, но код не крашится. В проде — потеря лейблов. Тест: Установи invalid upstream — увидишь ptr_errors++, но код продолжит. loadDNSConfig fallback на дефолт без валидации: Если файл битый (e.g., "default abc#invalid") — fallback на дефолт, но дефолт может не работать. Поломка: Silent fail — резолв будет работать на старых дефолтах, но юзер не поймёт почему. Тест: Сделай файл с кривыми upstream'ами — увидишь в логах fallback, но без ошибки. uniqueStrings и map в IPMap — но без сортировки в map: IPMap использует map[string]struct{} для dedup, но при append в res.IPMap — sort.Slice по IP, но лейблы внутри не отсортированы. Поломка: Не поломка, но нестабильный порядок лейблов в mapLines — если GUI полагается на порядок. Тест: Несколько лейблов на IP — порядок random. isPrivateIPv4 не обрабатывает /mask в IP: Проверяет только base IP, но если ip = "10.0.0.0/8" — parts = ["10.0.0.0/8"] → len!=4 → return true (ок). Но если mask invalid — не catch. Поломка: Если static с кривым CIDR (e.g., "1.2.3.4/33") — пропустит как non-private. Тест: Добавь "1.2.3.4/33" в static — пройдёт, хотя invalid. runResolverJob: если workers=0 или ttl=0 — не краш, но дефолты Ты clamp'ишь (ttl 60..86400, workers 1..500) — хорошо, но если envInt fail — silent. Поломка: Не поломка, но если ENV кривой — silent fallback. 2. Слабые места (не поломки, но риски в проде) Concurrent резолв без rate-limit: 80 workers + 500+ доменов — может flood upstream'ы (DNS rate-limit ban). Риск: Upstream забанит IP, все запросы fail. Нет IPv6: Всё только IPv4 (A-записи, isPrivateIPv4). Если домен только AAAA — no IPs. Риск: Современные сайты (e.g., google) имеют IPv6 — потеряешь трафик. Кэш без invalidation: Кэш на TTL, но если upstream сменился — старый кэш может быть invalid. Риск: После смены DNS — старые IPs в кэше до TTL. PTR без кэша ошибок: Если PTR fail — всё равно пишем в кэш empty names. Следующий раз — из кэша empty. Риск: Временный fail upstream — навсегда потерянные лейблы до ручного clear cache. Логи только в файл: appendTraceLine — ok, но если файл locked — silent fail. Риск: В многопоточном режиме — race на append. envInt без дефолта fallback: Если ENV empty — strconv.Atoi("") → error, но ты используешь default. Ok, но можно сделать safe. 3. Рекомендации и фиксы Вот конкретные советы + кодовые патчи. Внедряй по приоритету (сначала поломки). Фикс портов и TCP в resolveHostGo (для поломки 1): Используй net.Dialer с UDP/TCP fallback. Patch:Gofunc resolveHostGo(host string, cfg dnsConfig, metaSpecial []string, logf func(string, ...any)) ([]string, int) { // ... (твой код) var ips []string errs := 0 for _, upstream := range dnsList { server, port := splitDNS(upstream) if server == "" { errs++ continue } if port == "" { port = "53" } addr := net.JoinHostPort(server, port) // Сначала UDP ipsUdp, errUdp := lookupA(host, addr, "udp", 3*time.Second) if errUdp == nil { ips = append(ips, ipsUdp...) continue } else if logf != nil { logf("UDP fail for %s@%s: %v", host, addr, errUdp) } // Fallback TCP ipsTcp, errTcp := lookupA(host, addr, "tcp", 5*time.Second) if errTcp == nil { ips = append(ips, ipsTcp...) } else { errs++ if logf != nil { logf("TCP fail for %s@%s: %v", host, addr, errTcp) } } } // dedup seen := map[string]struct{}{} unique := []string{} for _, ip := range ips { if _, ok := seen[ip]; !ok && !isPrivateIPv4(ip) { seen[ip] = struct{}{} unique = append(unique, ip) } } return unique, errs } func lookupA(host, addr, network string, timeout time.Duration) ([]string, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() r := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, _, _ string) (net.Conn, error) { d := net.Dialer{} return d.DialContext(ctx, network, addr) }, } ips, err := r.LookupHost(ctx, host) if err != nil { return nil, err } var out []string for _, ip := range ips { if netip.MustParseAddr(ip).Is4() { out = append(out, ip) } } return out, nil } Увеличь timeout + retry в resolveHostGo (для поломки 2): Добавь retry для всей функции (3 попытки). Patch: Оберни loop по upstream в backoff.Retry (как в nft). Retry для PTR (для поломки 3): Patch в resolveStaticLabels:Go// Внутри if !names: for attempt := 0; attempt < 3; attempt++ { names, err = digPTR(base_ip, dns_for_ptr, 5*time.Second, logf) if err == nil { break } time.Sleep(500 * time.Millisecond) } if err != nil { ptrErrors++ } Валидация upstream в loadDNSConfig (для поломки 4): Patch:Gofunc loadDNSConfig(path string, logf func(string, ...any)) dnsConfig { // ... (твой код) valid := func(s string) bool { if s == "" { return false } _, err := netip.ParseAddrPort(s) return err == nil } cfg.Default = filter(cfg.Default, valid) cfg.Meta = filter(cfg.Meta, valid) if len(cfg.Default) == 0 { cfg.Default = defaultDNS if logf != nil { logf("dns-config: no valid default upstreams, fallback to built-in") } } // аналогично для meta } func filter(ss []string, fn func(string) bool) []string { var out []string for _, s := range ss { if fn(s) { out = append(out, s) } } return out } Сортировка лейблов в IPMap (для поломки 5): Patch в runResolverJob после for host, ips...:Gofor ip, labelsMap := range ipMap { var labels []string for lbl := range labelsMap { labels = append(labels, lbl) } sort.Strings(labels) for _, lbl := range labels { res.IPMap = append(res.IPMap, [2]string{ip, lbl}) } } Валидация CIDR в isPrivateIPv4 (для поломки 6): Patch: Используй netip.ParsePrefix:Gofunc isPrivateIPv4(ip string) bool { if strings.Contains(ip, "/") { p, err := netip.ParsePrefix(ip) if err != nil { return true // invalid → private (пропустить) } return p.IsPrivate() } a, err := netip.ParseAddr(ip) if err != nil { return true } return a.IsPrivate() } Rate-limit в concurrent (для слабого 1): Добавь semaphore в workers-loop:Gosem := make(chan struct{}, 20) // лимит 20 одновременных запросов for i := 0; i < workers; i++ { go func() { for j := range jobs { sem <- struct{}{} // resolve <-sem } }() } IPv6 поддержка (для слабого 2): Добавь AAAA в resolveHostGo (параллельно с A). Добавь isPrivateIPv6. Кэш invalidation (для слабого 3): Добавь флаг --force для игнора TTL. Логи в файл с sync (для слабого 5): В appendTraceLine добавь file.Sync() после Write. Итог Поломки: В основном edge-кейсы (медленный DNS, invalid upstream, большие ответы) — фиксы выше. Слабые: Масштаб (rate-limit, IPv6) и robustness (retry PTR, validate). Код в целом очень хороший — внедряй фиксы по приоритету (сначала порты/TCP, timeout/retry).