VPN на Android працює через IPv6, але браузер усе ще вибирає IPv4
Пройдіть чотири етапи, на яких зникає приватна адреса IPv6: резолвер, оголошення VpnService, перевірка кандидатів браузером і доставка маршруту.
VPN піднято, приватний веб-інтерфейс відповідає через IPv6 з ноутбука, а телефон на Android досі відкриває ту саму сторінку через IPv4 або взагалі відмовляється її завантажувати. Запис AAAA існує. Тунель повідомляє адресу IPv6. У клієнті ніщо не виглядає неправильно. Результат визначають чотири окремі рішення: який резолвер відповів, що VpnService оголосив Android, яку родину адрес браузер був готовий спробувати і чи може пакет пройти шлях туди й назад. Розбирайте їх у цьому порядку, а не додавайте маршрут за замовчуванням у надії, що симптом зсунеться.
Крок 1: доведіть, який резолвер відповів на запит AAAA
Android не має єдиного шляху DNS. VPN може проштовхнути DNS-сервер для своєї мережі, система може використовувати Private DNS для імен вузлів, які вважає придатними, а Chrome може запускати власний зашифрований резолвер з chrome://settings/security. На телефоні, де тунель проштовхує 10.61.0.1, але Private DNS указує на публічного постачальника, ім'я у вашій приватній зоні не розв'язує та машина, яка володіє зоною. Браузер отримує відповідь від резолвера без повноважень на це ім'я і вважає його відсутнім, а текст помилки ніколи не згадує DNS.
Запитайте резолвер тунелю напряму, перш ніж щось інше змінювати. Перші дві команди показують, які DNS-сервери Android прив'язав до активної мережі і чи Private DNS у автоматичному чи суворому режимі; третя виконується з будь-якого клієнта всередині тунелю, а не з телефона.
adb shell dumpsys connectivity | grep -A3 "DnsAddresses"
adb shell settings get global private_dns_mode
nslookup -type=AAAA nas.lan 10.61.0.1
Крок 2: прочитайте, що VpnService насправді оголосив
Бік Android є явним, а не виведеним. VpnService.Builder додає адреси через addAddress, маршрути через addRoute, DNS-сервери через addDnsServer і може обмежити весь тунель однією родиною адрес через allowFamily. Будівник, який викликав allowFamily(AF_INET), а згодом додав маршрут IPv6, сказав системі дві суперечливі речі, і система зберігає саме обмеження родини. Маршрут, що покриває fd00:61::/64, разом з адресою інтерфейсу в іншому префіксі не працює з подібної причини: адреса існує, але вона не on-link для цього маршруту.
Читайте оголошення, а не довіряйте перемикачу в застосунку. dumpsys netd друкує адреси й маршрути, прив'язані до інтерфейсу tun, а LinkProperties за кожною мережею всередині dumpsys connectivity показує список DNS поруч із родинами, які мережа заявляє.
adb shell ip -6 addr show tun0
adb shell ip -6 route show table all | grep tun0
adb shell dumpsys connectivity | grep -E "LinkAddresses|Routes"
Якщо інтерфейс tun має адресу IPv6, але жоден маршрут не відповідає префіксу служби, доставка IPv6 уже зламана до того, як у справу вступить браузер. Це інша помилка, ніж у браузера, який відмовляється пробувати, і вона потребує іншого виправлення.
Крок 3: браузер вирішує, чи взагалі пробувати IPv6
Chromium використовує не кожен отриманий запис AAAA. Його резолвер вузлів несе перевірки доступності IPv6 і фільтрування кандидатів, які можуть відкинути IPv6 або знизити його пріоритет, коли мережа визнана неспроможною його нести, а вихідний код резолвера показує цю перевірку разом з умовами навколо неї. Логіка залежить від версії та прапорців, і саме тому два телефони в одному тунелі можуть поводитися по-різному. Щойно кандидати з'являються, Happy Eyeballs влаштовує гонку родин для кожного з'єднання, тож повільно відповідний шлях IPv6 усе ще може програти швидко відповідному IPv4.
Спостерігайте за рішенням, а не виводьте його. Сеанс, записаний через chrome://net-export і відтворений у переглядачі netlog, показує кожне розв'язання та вибрану родину; chrome://net-internals/#dns показує результати з кешу. Розв'язання, яке повертає лише записи A для імені, що має AAAA на вашому власному резолвері, прямо вказує на цей етап.
Крок 4: переконайтеся, що пакет може пройти шлях туди й назад
Вибір адреси — це не доставка. Android тримає таблиці маршрутизації та правила політики за кожною мережею, які вибирають таблицю за fwmark, тому маршрут, видимий в основній таблиці, не обов'язково той, який використовує сокет. Читання правил і прохання до ядра розв'язати призначення для адреси джерела розділяють ці дві речі.
adb shell ip -6 rule show
adb shell ip -6 route get fd00:61::20 from fd00:61::10
Саме вивід route get відповідає на питання. Якщо він називає tun0, а пакети все ще гинуть, переходьте до MTU тунелю та пересилання IPv6 на шлюзі, а не назад до телефона.
Чому додавання ::/0 зазвичай робить гірше
Додавання ::/0 змінює претензію тунелю з одного префікса на кожне призначення IPv6. На телефоні це може задовольнити перевірку доступності, бо тестова адреса поза вашим префіксом тепер виглядає маршрутизованою. Але це не створює пропускну здатність IPv6 у висхідному напрямку на шлюзі, а багато домашніх шлюзів узагалі не мають робочого висхідного каналу IPv6. Результат — чорна діра для публічного IPv6: імена, які раніше відкочувалися на IPv4, тепер зависають, а приватна сторінка може виглядати краще лише тому, що перевірка вимірює щось інше.
Є й ціна для політики. З ::/0 у тунелі весь трафік IPv6 виходить через VPN, включно з трафіком, який власник мережі ніколи не погоджувався нести. Ставтеся до цього як до рішення про розгортання з названим відповідальним, а не як до кроку налагодження о 1 годині ночі.
Порядок роботи, який це завершує
Підтвердьте, що відповідь AAAA надходить від резолвера, який володіє зоною, прочитайте стан VpnService і netd для інтерфейсу tun, зніміть трасу net-export, щоб побачити, чи Chromium відкинув кандидата, а потім перевірте шлях туди й назад за допомогою ip -6 route get. Виправте той шар, який справді відмовив, і не чіпайте політику розділення. Якщо приватним іменам потрібні інші відповіді, ніж публічним, для цього й існує split tunneling.
Запишіть збірку телефона, версію браузера та резолвер, який ви опитували. Ці три факти перетворюють "IPv6 is broken" на те, що наступна людина зможе відтворити.