آیپد روی Tailscale بود؛ صفحه سفید موند
خلاصهٔ کاملتر
نویسنده تعریف میکنه که یکی از اپهای p2claw ـش رو روی آیپد باز کرده و فقط یه صفحهی سفید گرفته، با اینکه همون آدرس روی Mac و یه باکس لینوکس و گوشیش درست کار میکرده؛ همون wifi، همون موتور مرورگر، همون شبکه. اپ بهاندازهی نمایش حالت loading لود میشده و بعد قفل میکرده. جالب اینکه یه heisenbug بوده: اگه دیوانهوار refresh میکرده، بعضی وقتا صفحه میومده بالا.
اتصال WebRTC کامل برقرار میشده و data channel باز میشده، ولی اولین GET / که مرورگر روی کانال میفرستاده، جوابش هیچوقت نمیرسیده. agent سمت باکس فکر میکرده همهچی مرتبه و بایتها رو روی کانال گذاشته، ولی به آیپد نمیرسیدن. نویسنده میگه با لاگ گرفتن از هر دو سر اتصال و ردیف کردن لاگها بر اساس ساعت، فهمیدن داده دقیقاً کجا گم میشه: بافر خروجیِ فرستنده تا حدود ۸ کیلوبایت بالا میرفته و گیر میکرده.
اولش گمان بردن مشکل از WebKit ـه، چون روی همهی مرورگرهای iOS (که زیرِ پوست همه WebKit هستن) اتفاق میفتاده. ولی بعد از دو ساعت نویسنده میبینه فرق آیپد با Mac اینه که آیپد Tailscale روشن داشته. Tailscale یه VPN ـه و ترافیک رو توی یه لایهی اضافه میپیچه، پس جای کمتری توی هر بسته میمونه و جوابهای بزرگ به تیکههای کوچیکتر شکسته میشن. نویسنده اعتراف میکنه همینجا سرنخ درست رو داشتن ولی بهاشتباه چپوندنش توی همون تئوریِ WebKit.
ریشهی اول توی کتابخونهی Rustی به اسم webrtc-rs بود که باکس ازش استفاده میکنه. این کتابخونه بستههاشو بر اساس یه ثابتِ hardcode و غیرقابلتنظیم میبُره:
pub(crate) const INITIAL_MTU: u32 = 1228;با لایهی رمزنگاری و سرآیندهای UDP/IP، این عدد روی سیم به حدود ۱۲۹۳ بایت روی IPv4 یا ۱۳۱۳ بایت روی IPv6 میرسه، در حالی که تونل Tailscale حداکثر ۱۲۸۰ بایت رو جا میده. نویسنده میگه بزرگ بودن بسته بهخودیِخود کشنده نیست؛ کرنل بستهی بزرگ رو fragment میکنه و دو تیکه میفرسته که قاعدتاً باید اونور سرِهم بشن.
مشکل اینجا بود که قطعات کجا گم میشن. نویسنده با یه آزمایش سادهی ping بدون نیاز به WebRTC قضیه رو قطعی کرد: یه ping کوچیک رد میشد ولی یه ping بزرگ که مجبور به fragment شدن میشه، روی IPv6 صددرصد گم میشد و روی IPv4 سالم میرسید:
ping -s 100 <ipad over IPv6> 3/3 received
ping -s 1400 <ipad over IPv6> 0/3, 100% loss
شمارندههای خود Tailscale هم لو دادن چی شده: متریکِ tailscaled_inbound_dropped_packets_total با دلیل acl بالا میرفت، یعنی فیلترِ بسته اونها رو بهعنوان نقضِ سیاست دور انداخته — اون هم روی یه tailnet شخصی که سیاستش «همهچی مجازه» بوده. نویسنده میگه با نگاه به سورسِ متنبازِ Tailscale معلوم شد پارسرِ IPv6 ـشون اصلاً fragmentها رو پارس نمیکنه و هر بستهای با هدرِ Fragment رو «پروتکل ناشناخته» حساب میکنه که به هیچ قانونِ allow نمیخوره و در نتیجه deny پیشفرض شلیک میشه. بهنظرِ نویسنده این یه برداشتِ غلط از استانداردِ IPv6 ـه: IPv6 فقط fragment کردن توسط روترهای میانی رو ممنوع کرده، نه fragment کردن توسط فرستنده.
به گفتهی نویسنده درسِ ماجرا اینه که اگه داری چیزی مثل تماس تصویری، بازی یا اپهای peer-to-peer میسازی، فرض رو بذار که بخشی از کاربرات روی مسیری با ظرفیتِ کوچیکتر از انتظارت هستن؛ یا بستههاتو محافظهکارانه کوچیک نگه دار یا قبل از اعتماد، مسیر رو probe کن. هیچکدوم از این دو پروژه کارِ عجیبی نکرده بودن، ولی کنارِ هم یه تلهی بدونِ پیامِ خطا ساختن که تنها نشونهش یه صفحهی سفید روی یه دستگاهِ خاص بود.
نکات کلیدی:
- صفحهی سفید فقط روی آیپد بود چون فقط اون Tailscale روشن داشت و مسیرِ IPv6 تونل رو انتخاب میکرد
- ثابتِ INITIAL_MTU = 1228 توی webrtc-rs بستهها رو کمی بزرگتر از ظرفیتِ ۱۲۸۰ بایتیِ تونل میکرد
- بستههای بزرگ fragment میشدن، ولی Tailscale قطعاتِ IPv6 رو با دلیلِ acl بیصدا دور میانداخت
- IPv4 قطعات رو درست سرِهم میکرد، برای همین باگ گاهی ظاهر میشد و گاهی نه (heisenbug)
- یه ping بزرگ روی IPv6 سادهترین راهِ بازتولیدِ مشکله؛ هر دو باگ بهصورت issue گزارش شدن




