Симптом: «Ошибка VPN» с DNS-200/TARGET_NETWORK_TIMEOUT, где сурфейсилась
core-строка `open connection to 172.19.0.2:853 using outbound/direct[direct]:
dial wlan0: i/o timeout`.
Причина: Android получает адрес самого TUN (напр. 172.19.0.2) как сетевой DNS,
а перехватываем мы там только порт 53. Оппортунистический Private DNS и
встроенные резолверы приложений всё равно проверяют этот приватный адрес по
DoT/DoH (:853) на других портах. Такой пакет попадал под политику
`ip_is_private → direct` и дозванивался по физическому линку, где адреса TUN
нет: приложение висело весь таймаут соединения, а этот direct-фейл всплывал
ложной причиной отказа туннеля.
Фикс (два уровня):
- route: сразу за hijack-dns генерируется reject-правило на собственную
подсеть TUN (ip_cidr из фактического адреса профиля, без хардкода). Порт 53
уже перехвачен раньше, любой другой порт к sink получает мгновенный RST —
корректный сигнал «здесь DoT нет», клиент откатывается на plaintext 53 сразу.
Правило — инфраструктурный инвариант: регенерируется и дедуплицируется на
каждом запуске. Ограничено адресами TUN, поэтому внешний strict Private DNS
DoT не затрагивается.
- диагностика: соединения к диапазону sink классифицируются как
LOCAL_DNS_SINK_UNREACHABLE (record_only) и больше не подменяют настоящую
причину отказа и не триггерят ложный failover.
Тесты: позиция/идемпотентность sink-reject в RuntimeConfigBuilderTest,
record_only-классификация в RuntimeErrorsTest. Обновлён DNS_ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>