Leadaxe:prefer_ipv6 不改变客户端 DNS 应答,拟新增 opportunistic_ipv6_only 策略

在 sing-box-lx #27(新增 DNS 策略的功能请求,仍 open)中,Leadaxe 说明为什么 prefer_ipv6 解决不了问题:它只在内核内部解析(Lookup)时生效,内核自己查 A/AAAA 并把 IPv6 地址排在最前;而 TUN 下的应用自己发查询,走的是 Exchange,A 与 AAAA 是两次独立请求,丢弃查询只发生在 ipv4_only / ipv6_only,prefer_ipv6 下 A 请求原样返回,应用拿到两个地址后自行选族(浏览器或系统的 Happy Eyeballs 会把一部分连接走 IPv4)。他认同新增一条 opportunistic_ipv6_only:域名同时有 A/AAAA 时只回 AAAA,没有 AAAA 时照常回 A,用来替代「ipv6_only 域名清单 + 其余 prefer_ipv6」的组合;域名有 AAAA 时还要从 HTTPS/SVCB 记录里去掉 ipv4hint。风险是链路上没有可用 IPv6 时所有带 AAAA 的域名都会不可达(需在文档单独警示),以及每个 A 请求多一次 AAAA 查询(有缓存与去重后开销接近零);它只作用于 DNS 规则与 dns.strategy,不会成为 outbound 的通用 domain_strategy。issue 仍为 open,尚未实现。原帖注明该分析由其 Claude agent 代维护者整理。

作者原文

🤖 Анализ подготовлен AI-агентом Claude (Anthropic).

Что предлагается

Нужна DNS-стратегия, условно opportunistic_ipv6_only: если у домена есть и A, и AAAA, клиенту отдаётся только AAAA. Если AAAA нет, отдаётся A как обычно, чтобы v4-only сайты продолжали работать. Одно правило заменяет пару «ipv6_only для списка доменов + prefer_ipv6 для остальных», и список доменов больше не нужно поддерживать вручную.

Почему prefer_ipv6 не помогает

prefer_ipv6 работает только при внутреннем резолве ядра (Lookup). Там ядро само запрашивает A и AAAA и ставит IPv6-адреса первыми:
https://github.com/Leadaxe/sing-box-lx/blob/6a606c8b8b52c6742874fe5378481c48244a387c/dns/client.go#L517-L523

Приложения за TUN отправляют собственные DNS-запросы, и те обрабатываются через Exchange. A и AAAA приходят туда отдельными запросами. Стратегия отбрасывает запрос только в режимах ipv4_only/ipv6_only:
https://github.com/Leadaxe/sing-box-lx/blob/6a606c8b8b52c6742874fe5378481c48244a387c/dns/client.go#L271-L276

При prefer_ipv6 A-запрос проходит без изменений. Приложение получает оба адреса и само выбирает семейство: Happy Eyeballs в браузере или ОС ведёт часть соединений по IPv4. Поэтому Google и видит IPv4-адрес. Для трафика приложений prefer_ipv6 по сути ничего не меняет, и отдельная стратегия здесь действительно нужна.

Предлагаемое поведение

  • A-запрос: ядро проверяет AAAA для того же имени (в кэше или параллельным запросом). Если AAAA непустой, клиент получает пустой ответ NOERROR, как сейчас при ipv6_only. Если пустой, возвращается обычный ответ A.
  • AAAA-запрос: возвращается без изменений.
  • Внутренний резолв (Lookup): если IPv6-адреса есть, возвращаются только они, иначе IPv4.
  • HTTPS/SVCB-записи: если у домена есть AAAA, из них вырезается ipv4hint. Иначе браузер может взять IPv4 из HTTPS-записи в обход A. Для ipv6_only это уже сделано:
    https://github.com/Leadaxe/sing-box-lx/blob/6a606c8b8b52c6742874fe5378481c48244a387c/dns/client.go#L661-L676

Риски и ограничения

  1. Нет рабочего IPv6 на пути. Если outbound или сервер не пропускает IPv6, все домены с AAAA станут недоступны, хотя IPv4 у них есть. Стратегия включается явно, но в документации об этом нужно предупредить отдельно.
  2. Дополнительный AAAA-запрос на каждый A. На практике приложения обычно запрашивают A и AAAA параллельно, поэтому при кэше и дедупликации одинаковых запросов накладные расходы близки к нулю.
  3. Область действия. Стратегия имеет смысл только для DNS: в DNS-правилах и в dns.strategy. Для domain_strategy у outbound'ов и резолва в маршрутизации её семантика не нужна, поэтому она не должна становиться общим значением DomainStrategy.

Вывод

Запрос обоснован: существующие стратегии не решают задачу для трафика приложений, потому что prefer_ipv6 не влияет на ответы клиентам. Реализация локальная и затрагивает обработку DNS-запроса и внутренний резолв. Основной риск — недоступность доменов с AAAA, если на пути нет рабочего IPv6. Это нужно явно описать в документации.