VPN اندروید با IPv6 کار میکند، اما مرورگر هنوز IPv4 را انتخاب میکند
چهار مرحلهای را دنبال کنید که یک نشانی IPv6 خصوصی ناپدید میشود: resolver، اعلان VpnService، بررسی نامزدهای مرورگر و تحویل مسیر.
VPN بالا است، رابط وب خصوصی از یک لپتاپ روی IPv6 پاسخ میدهد، و گوشی اندروید همچنان همان صفحه را روی IPv4 باز میکند یا از بارگذاری آن بهکلی امتناع میکند. رکورد AAAA وجود دارد. تونل نشانی IPv6 گزارش میکند. هیچ چیز در کلاینت اشتباه به نظر نمیرسد. چهار تصمیم جداگانه نتیجه را تعیین میکنند: کدام resolver پاسخ داد، VpnService چه چیزی را به اندروید اعلان کرد، مرورگر حاضر بود کدام خانواده نشانی را امتحان کند، و آیا یک بسته میتواند سفر رفت و برگشت را انجام دهد. آنها را به همین ترتیب پیش ببرید، نه اینکه یک مسیر پیشفرض اضافه کنید و امیدوار باشید نشانه جابهجا شود.
گام 1: ثابت کنید کدام resolver به پرسش AAAA پاسخ داد
اندروید یک مسیر DNS واحد ندارد. یک VPN میتواند سرور DNS را برای شبکه خود هل بدهد، سیستم ممکن است برای نام میزبانهایی که واجد شرایط میداند از Private DNS استفاده کند، و Chrome میتواند resolver رمزگذاریشده خود را از chrome://settings/security اجرا کند. در گوشیای که تونل 10.61.0.1 را هل میدهد اما Private DNS به یک ارائهدهنده عمومی اشاره میکند، نام داخل منطقه خصوصی شما توسط ماشینی که مالک منطقه است حل نمیشود. مرورگر از resolverی پاسخ میگیرد که اختیاری برای آن نام ندارد و آن را غایب میپندارد، و متن خطا هرگز به DNS اشاره نمیکند.
پیش از تغییر هر چیز دیگری، مستقیماً از resolver تونل بپرسید. دو فرمان نخست نشان میدهند اندروید کدام سرورهای DNS را به شبکه فعال متصل کرده و 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 واقعاً چه اعلان کرده است
سمت اندروید صریح است، نه استنتاجی. 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 را که دریافت میکند به کار نمیبرد. resolver میزبان آن بررسیهای دسترسپذیری IPv6 و پالایش نامزدها را دارد که میتوانند IPv6 را کنار بگذارند یا اولویت آن را پایین بیاورند وقتی شبکه ناتوان از حمل آن تشخیص داده شود، و کد منبع resolver آن آزمون را همراه با شرایط پیرامونش نشان میدهد. منطق به نسخه و پرچمها وابسته است، و دقیقاً به همین دلیل دو گوشی روی یک تونل میتوانند رفتار متفاوت داشته باشند. وقتی نامزدها وجود دارند، Happy Eyeballs برای هر اتصال بین خانوادهها مسابقه میگذارد، پس مسیر IPv6 که کند پاسخ میدهد میتواند همچنان به IPv4 که سریع پاسخ میدهد ببازد.
تصمیم را تماشا کنید نه استنتاج. نشستی که با chrome://net-export ضبط و در نمایشگر netlog بازپخش میشود هر حل و خانواده انتخابشده را نشان میدهد؛ chrome://net-internals/#dns نتایج ذخیرهشده در حافظه نهان را نشان میدهد. حلکردنی که برای نامی با AAAA در resolver خودتان فقط رکوردهای A برمیگرداند مستقیماً به این مرحله اشاره میکند.
گام 4: بررسی کنید که یک بسته میتواند سفر رفت و برگشت را انجام دهد
انتخاب نشانی تحویل نیست. اندروید جدولهای مسیریابی و قواعد سیاست بهازای هر شبکه نگه میدارد که جدول را با 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 از resolverی میآید که منطقه را در اختیار دارد، وضعیت VpnService و netd را برای رابط tun بخوانید، یک رد net-export بگیرید تا ببینید Chromium نامزد را کنار گذاشته یا نه، سپس سفر رفت و برگشت را با ip -6 route get بیازمایید. لایهای را که واقعاً شکست خورده اصلاح کنید و سیاست تقسیم را دست نزنید. اگر نامهای خصوصی پاسخهای متفاوتی از نامهای عمومی میخواهند، split tunneling دقیقاً برای همین است.
ساخت گوشی، نسخه مرورگر و resolverی را که پرسیدید یادداشت کنید. آن سه واقعیت "IPv6 is broken" را به چیزی تبدیل میکند که نفر بعدی میتواند بازتولید کند.