پرش به محتویات

رفع اشکال‌ها در sing-box هیدیفای نسخهٔ ۵

اشکال‌هایی که در فورک هیدیفای، روی sing-box نسخهٔ v1.15، رفع شده‌اند. جدیدترین مورد در بالاست و هر مورد به کامیتی که آن را رفع کرده اشاره می‌کند.

همهٔ قابلیت‌های هیدیفای


یک پروکسی چند بار هم‌زمان پینگ می‌شد و lowest-delay به پروکسی کندتری می‌رفت

کامیت: 635d533ac

چه می‌شد: ضربه روی یک پروکسی، یا رفتن انتخاب گروه به آن، در حالی که تست دوره از آن در جریان بود تست دیگری شروع می‌کرد، و تست مرحله‌ای کنار تلاش اول کند، تلاش دوم و سومی شروع می‌کرد: پروکسی دو سه بار پینگ می‌شد و هر تستی که دیرتر تمام می‌شد نتیجه را می‌نوشت، پس شکستی دیرهنگام جای پاسخ تازه را می‌گرفت و گروه‌ها عضو را رها می‌کردند. پس از شروع یا بارگذاری دوباره، وقتی همهٔ نتیجه‌ها کش‌شده‌اند، اولین عضوی که پاسخ می‌داد انتخاب lowest-delay را با ۹۱ میلی‌ثانیه از عضو ۱۷ میلی‌ثانیه‌ای که هنوز در حال تست بود می‌گرفت و لحظه‌ای بعد پس می‌داد؛ هر جابه‌جایی انتخاب تازه را چند ثانیه پس از اندازه‌گیری دوباره تست می‌کرد و زیر بار آن پینگ دوم برای عضوی ۵۹ میلی‌ثانیه‌ای ۲۵۴۹ می‌خواند. عضوی که وقتی عضو سریع پاسخ نمی‌داد انتخاب شده بود تا پایان دوره نگه داشته می‌شد، مدت‌ها پس از پاسخ دوبارهٔ عضو سریع. هیدیفای‌کور تلرانس مطلق ۱۰۰ میلی‌ثانیه می‌فرستاد که عضو ۵۹ میلی‌ثانیه‌ای را بر عضو ۲۵ میلی‌ثانیه‌ای نگه می‌داشت.

رفع: هر خروجی هر بار یک تست URL: تستی که وقتی تستی در جریان است درخواست شود به آن می‌پیوندد و نتیجه‌اش را می‌گیرد. تست مرحله‌ای فقط تلاش ناموفق را در مرحلهٔ بعد تکرار می‌کند و برای خروجی‌ای که در این اجرا کار کرده مرحلهٔ اول را رد می‌کند. lowest-delay در دوره فقط تا وقتی نگه می‌دارد که دوره عضوِ در حال استفاده را تست کند. انتخابی که در ۱۵ ثانیهٔ گذشته اندازه گرفته شده دوباره تست نمی‌شود. خط‌های لاگ MONITORING و LOWEST می‌گویند هر تست چرا اجرا می‌شود و گروه چه تصمیمی می‌گیرد. هیدیفای‌کور تلرانس ۲۰ میلی‌ثانیه با نسبت ۰٫۳ می‌فرستد و تگ پروکسی‌های پروفایل را میان به‌روزرسانی‌هایش ثابت نگه می‌دارد، پس بارگذاری دوباره پروکسی‌های بی‌تغییر و پینگ‌هایشان را نگه می‌دارد. سپس (a85a71b1a): نتیجه‌های دورهٔ اول یکی‌یکی می‌رسند و انتخاب هنوز با هر نتیجهٔ سریع‌تر می‌پرید، در چهل ثانیه از ۴۳۹ میلی‌ثانیه به ۱۷۰، ۱۱۲، ۴۱ و ۱۸. عضوی که با پینگ واقعی انتخاب را گرفته تا وقتی پاسخ می‌دهد ۳۰ ثانیه می‌ماند و بعد بهترینِ شناخته‌شده جایش را می‌گیرد. پینگی که از اجرای قبل کش شده هیچ تصمیمی نمی‌گیرد و نشان داده نمی‌شود: فقط عضوها را تا رسیدن پینگ‌های واقعی مرتب می‌کند، و فهرست برنامه با آن (sort_delay) مرتب می‌شود بی‌آنکه نشانش دهد.

با اشتراکی بزرگ، پینگ‌ها هر چند ثانیه عوض می‌شدند

کامیت: 635d533ac

چه می‌شد: با اشتراکی رایگان از صد کانفیگ یا بیشتر، پینگی که برنامه نشان می‌داد هر چند ثانیه بالا و پایین می‌رفت (۲۹، ۴۴، ۳۳ میلی‌ثانیه). سه چیز همان پروکسی‌ها را بارها دوباره تست می‌کرد. متعادل‌کنندهٔ round robin هر بار که عضوهایی که میانشان می‌چرخد عوض می‌شدند، که تقریباً با هر نتیجهٔ یک دوره است، انتخاب تازه را به پایش خبر می‌داد و هر خبر، والدهایش (hop امنیت بیشتر) و انتخاب بعدی‌اش را دوباره تست می‌کرد. عضوی که تازه کار می‌کرد، هر چیزی را که از هر گروهِ آن عضو اندازه گرفته شده بود دوباره تست می‌کرد، hop پشت selector را هم، چه گروه آن عضو را به کار می‌برد چه نه. و هر دورهٔ منظم انتخاب را با هر نتیجه جابه‌جا می‌کرد. در دقیقه‌های اول روی شبکهٔ ناپایدار، صفحهٔ اصلی می‌گفت پروکسی انتخاب‌شده پاسخ نمی‌دهد در حالی که پروکسی‌های دیگر کار می‌کردند: lowest-delay روی عضوی می‌ماند که از اجرای قبل کش شده بود، چون ناموفقیِ موقتِ تست مرحله‌ای پشت آخرین نتیجهٔ تأییدشده از گروه‌ها پنهان می‌شد، که برای آن عضو همان نتیجهٔ کش‌شده بود و حتی تازه علامت می‌خورد.

رفع: فقط متعادل‌کننده‌ای که همهٔ اتصال‌ها را از یک عضو می‌فرستد (lowest-delay) انتخاب تازه را به پایش خبر می‌دهد. عضوی که تازه کار می‌کند، آنچه را از یک گروه می‌گذرد فقط وقتی دوباره تست می‌کند که گروه همان عضو را به کار ببرد. هنگام دورهٔ منظمِ زمان‌بندی، lowest-delay و round robin عضوهایی را که به کار می‌برند تا وقتی پاسخ می‌دهند نگه می‌دارند و در پایان دوره با همهٔ نتیجه‌ها انتخاب می‌کنند (پایش به همهٔ گروه‌ها خبر می‌دهد)؛ دورهٔ اول و دوره‌ای پس از تغییر شبکه نتیجه‌ها را همان‌طور که می‌رسند به کار می‌برند. ناموفقیِ عضوی که از این اجرا جز نتیجهٔ کش‌شده نتیجه‌ای ندارد را گروه‌ها می‌بینند، پس lowest-delay آن را برای عضوی که پاسخ می‌دهد رها می‌کند. برنامه گروه round robin را با عضوی نشان می‌دهد که فقط عضوی بیش از تحمل lowest-delay سریع‌تر جایش را می‌گیرد. روی شبیه‌ساز اندروید با اشتراکی ۱۶۰تایی و امنیت بیشتر، تست‌های فوری hop در سه دقیقه از ۶۵ به ۰ رسید، و پینگ نمایش‌داده‌شده پس از چند ثانیهٔ اول ثابت ماند.


WARP وقتی هنگام تغییر شبکه از نو شروع می‌شد، تونل بسته‌ای را از کار می‌انداخت

کامیت: 00f7b6e63

چه می‌شد: پس از تغییر شبکه، نقطهٔ پایانی WARP که اسکنر دارد نقطهٔ پایانیِ در حال استفاده را در پس‌زمینه بررسی می‌کند و اگر پاسخ ندهد دنبال نقطهٔ دیگری می‌گردد. اگر WARP در همین حین از نو شروع می‌شد (دستگاهِ ردشده، عوض شدن تونل)، شروع دوباره وضعیت اسکنرِ نقطهٔ پایانی را پاک می‌کرد در حالی که بررسیِ شروعِ قبلی هنوز در جریان بود. اسکن برای ذخیرهٔ امتیازها این وضعیت را دوباره می‌خواند و به اشاره‌گر nil می‌خورد و کل فرایند از کار می‌افتاد: در iOS، تونل بسته‌ای. اولین اسکن هنگام اتصال هم همین کار را می‌کرد و probe آن وضعیت را دوباره می‌خواند (h_failover.go:258 در 5.5.1): در ویندوز، درست پس از اتصال روی شبکهٔ فیلترشده، وقتی ثبت‌نامِ ردشده WARP را از نو شروع می‌کرد («register WARP masque device: context canceled»).

رفع: هر بررسی وضعیت اسکنرِ شروعِ خودش را یک بار می‌خواند و تا آخر با همان کار می‌کند؛ اسکن، پروب‌ها، نویزهای نامزد و امتیازها آن را از بررسی می‌گیرند و دوباره از نقطهٔ پایانی نمی‌خوانند، و این وضعیت زیر قفل نقطهٔ پایانی خوانده و نوشته می‌شود. یک آزمون این وضعیت را وسط اسکن پاک می‌کند، همان کاری که شروع دوباره می‌کند؛ پیش از رفع از کار می‌افتاد.


قاعدهٔ DNS با bypass_if_failed برای هر پرسشی که به قاعدهٔ بعدی می‌داد خطا ثبت می‌کرد

کامیت: b1ec924c5

چه می‌شد: وقتی سرور قاعده‌ای با bypass_if_failed ناموفق می‌شد، قاعدهٔ بعدی به پرسش پاسخ می‌داد، ولی ناموفق شدن باز هم در سطح خطا ثبت می‌شد. قاعدهٔ Fake DNS در hiddify-core با bypass_if_failed هر پرسشی جز A و AAAA (HTTPS، TXT، ...) را از سرور fake IP به قاعدهٔ remote می‌دهد، پس یک مرورگر لاگ را از خطای «only IP queries are supported by fakeip» پر می‌کرد.

رفع: ناموفق شدنی که قاعدهٔ بعدی آن را به عهده می‌گیرد در سطح debug ثبت می‌شود. ناموفق شدن بدون bypass همچنان خطاست. دو آزمون هر دو حالت را بررسی می‌کنند.


خروجی masterdns از چند resolver روی یک IP فقط یکی را به کار می‌برد

کامیت: 0e8ed7f69

چه می‌شد: وقتی smart_pool خاموش است، خروجی resolverهایش را به کلاینت MasterDnsVPN می‌دهد که در ورودی کتابخانه‌اش برای هر IP فقط یک resolver نگه می‌داشت. resolverهای روی یک IP با درگاه‌های متفاوت (127.0.0.1:5301، 127.0.0.1:5302، یا سروری که روی بیش از یک درگاه پاسخ می‌دهد) یک resolver می‌شدند، پس توازن و تکرار بستهٔ کلاینت چیزی برای پخش کردن نداشت.

رفع: MasterDnsVPN (فورک هیدیفای، 62e94f1) هر IP و درگاه را resolver جداگانه‌ای نگه می‌دارد؛ بارگذار فایل resolverها رفتار قبلی‌اش را دارد. یک آزمون یک IP با سه درگاه را بررسی می‌کند.


ساخت پیش‌فرض mKCP، Sudoku، Bond و تونل تماس را نداشت

کامیت: 1c6b0ec6f

چه می‌شد: تگ‌های پیش‌فرض Makefile قرار بود with_sudoku، with_call، with_bond و with_kcp را اضافه کنند، ولی با / به جای , به هم چسبیده بودند. Go این چهار را یک تگ ناشناخته می‌خواند؛ پس ساخت با make و آزمون‌هایی که تگ‌های Makefile را به کار می‌برند هیچ‌کدام را نداشتند و پروفایل دارای mKCP با خطای «mKCP is not included in this build» شکست می‌خورد.

رفع: تگ‌ها با کاما جدا شده‌اند. همهٔ آزمون‌ها با روشن بودن آن‌ها می‌گذرند.


لینک‌هایی با ماسک merge، dialMode یا hellochrome_120 در Xray کنار گذاشته می‌شدند یا خطا می‌دادند

کامیت: 502f16cfa

چه می‌شد: لینک‌های Xray-core نسخهٔ patterniha (و ساب‌هایی که از آن‌ها ساخته می‌شوند) سه چیز به کار می‌برند که این فورک نمی‌شناخت. ماسک merge در finalmask کل لینک را از بارگذاری باز می‌داشت و لینک از پروفایل حذف می‌شد. dialMode=oob.0 نادیده گرفته می‌شد. نام‌های نسخه‌دار fingerprint در Xray مثل fp=hellochrome_120 خروجی را با خطای «unknown uTLS fingerprint» از کار می‌انداختند.

رفع: finalmask ماسک merge و ماسک oob را دارد؛ دومی همان dialMode با شکل oob. در Xray است که اولین نوشتن‌ها را با یک بایت اضافه به‌صورت دادهٔ فوری TCP می‌فرستد (منتقل‌شده از Xray-core نسخهٔ patterniha با مجوز MPL 2.0). uTLS همهٔ نام‌های fingerprint فهرست‌های Xray را، با هر حالت حروف، پس از نام‌های خود sing-box می‌پذیرد.


پایش: پراکسی‌ها پس از بارگذاری داغ در حالت «انتظار» می‌ماندند و در بیکاری دیگر آزمایش نمی‌شدند

کامیت: pending

چه می‌شد: سه چیز ردیف‌های صفحهٔ پراکسی‌ها را برای همیشه روی انیمیشن انتظار نگه می‌داشت. بارگذاری داغ (به‌روزرسانی پروفایل، تغییر یک تنظیم) خروجی‌هایی را که تنظیماتشان عوض شده جایگزین می‌کند؛ هر کدام بدون هیچ نتیجه‌ای از نو شروع می‌شد و Reload فقط وقتی می‌توانست چرخهٔ آزمون را آغاز کند که چرخه‌ای در حال اجرا نباشد. در پروفایلی با صدها پراکسی یک چرخه چند دقیقه طول می‌کشد، پس جایگزین‌شده‌ها تا تیک بازهٔ بعد منتظر می‌ماندند. خود تیک‌ها هم وقتی هسته به اندازهٔ زمان بیکاری (سه بازه، با پیش‌فرض‌های هیدیفای ۳۰ دقیقه) هیچ اتصالی را هدایت نکرده باشد متوقف می‌شوند و تماشای فهرست در اپ فعالیت به حساب نمی‌آمد؛ پس با ترافیک کم دیگر هیچ چیز آزمایش نمی‌شد. ضربه روی یک ردیف همان یک ردیف را آزمایش می‌کرد و چنین به نظر می‌رسید که فقط پراکسی کلیک‌شده پینگ می‌گیرد. و Touchای که کارگرهای متوقف‌شده را دوباره راه می‌انداخت زمان فعالیت قبلی را دست‌نخورده می‌گذاشت، پس تیک بعدی دوباره آن‌ها را متوقف می‌کرد.

رفع: خروجی جایگزین‌شده آخرین نتیجه‌اش را به عنوان نتیجهٔ کش‌شده و نیازمند آزمون نگه می‌دارد، همان‌طور که نتیجهٔ بارشده در آغاز چنین است: گروه‌ها می‌توانند به آن تکیه کنند و اپ آن را در حالت انتظار نشان می‌دهد. سپس بارگذاری مجدد فقط خروجی‌هایی را که ساخته یا جایگزین کرده می‌آزماید، همان لحظه و چه چرخه‌ای در حال اجرا باشد چه نباشد؛ بقیه نتیجهٔ خود را تا زمان عادی‌شان نگه می‌دارند. راه‌اندازی دوبارهٔ کارگرها فعالیت به حساب می‌آید. hiddify-core تا وقتی جریان پراکسی‌های اپ باز است پایش را فعال نگه می‌دارد.


IPv6 بر اساس خروجی: سایت‌های IPv6-اول از پراکسی بدون IPv6 همچنان می‌مردند

کامیت: d992de248

چه می‌شد: آزمون IPv6ای که ۵.۴.۰ افزوده بود یک اتصال به نشانی IPv6 یک resolver عمومی از طریق خروجی باز می‌کرد و باز شدن اتصال را نشانهٔ داشتن IPv6 می‌گرفت. dial در VLESS، Trojan، VMess و Shadowsocks پیش از آنکه سرور پراکسی به جایی وصل شده باشد برمی‌گردد، پس آزمون برای هر چنین پراکسی‌ای جواب مثبت می‌داد، پاسخ‌های AAAA داده می‌شد، کروم، مرورگر سامسونگ و اپ یوتیوب با IPv6 به گوگل می‌رسیدند، پراکسی اتصال را می‌بست و صفحه ERR_CONNECTION_CLOSED نشان می‌داد، در حالی که تلگرام و اینستاگرام که با IPv4 می‌رفتند کار می‌کردند (hiddify-app نسخه‌های ۵.۳ و ۵.۴ روی اندروید ۱۶ و ۱۷، #2420). آزمون خودش هم ترافیکی به نشانی‌های گوگل و کلادفلر بود که خروجیِ فیلترشده آن را می‌شکند، و reset پس از تلاش پراکسی هم کمکی نمی‌کرد: برنامه فقط وقتی به IPv4 برمی‌گردد که خودِ connect شکست بخورد، و از TUN، connect پیش از آنکه پراکسی چیزی را امتحان کرده باشد، محلی موفق می‌شود.

رفع: آزمون حذف شد (ipv6_probe بازنشسته است). IPv6 هر خروجی در تست URL آن بررسی می‌شود، با گرفتن دوبارهٔ همان URL تستِ خود کاربر از نشانی IPv6 میزبان (verify_ipv6)، یک بار در هر اجرا و با ذخیره برای هر خروجی؛ لینک اشتراکی می‌تواند آن را اعلام کند (ipv6=1، ipv6=0، ipv6_declared)؛ و اتصال TCP به نشانی IPv6 از خروجیِ هنوز ناشناخته را مسیریاب زیر نظر می‌گیرد (route.ipv6_fallback): وقتی خروجی چیزی برنگرداند، همان خروجی با دامنهٔ sniff‌شده dial می‌شود، بایت‌های برنامه دوباره گفته می‌شود و اتصال ادامه می‌یابد، بی‌آنکه برنامه شکستی ببیند، و نتیجه IPv6 خروجی را یاد می‌دهد. ipv6_by_outbound و ipv6_unavailable_via دوباره fail-open هستند: خروجیِ هنوز ناشناخته رد می‌شود و خروجی‌ای که معلوم است IPv6 ندارد پاسخ AAAA نمی‌گیرد و اتصال‌های IPv6اش پیش از handshake رد می‌شوند. hiddify-core ‏IPv6 را برای برنامه‌ها به‌طور پیش‌فرض خاموش می‌کند و همهٔ این‌ها را وقتی روشن می‌کند که حالت IPv6 روشن شود. نگاه کنید به IPv6 بر اساس خروجی.

Naive: بازبینی اپ‌استور نسخهٔ macOS را به خاطر API غیرعمومی رد کرد

کامیت: در انتظار

چه می‌شد: بازبینی اپ‌استور اپ macOS را رد کرد، چون hiddify-core.dylib و packet tunnel به _kCFBundleNumericVersionKey ارجاع می‌دادند که اپل آن را API غیرعمومی می‌داند. این ارجاع از تابع base::mac::OuterBundleCachedInfoPlistData() کرومیوم (info_plist_data.o) در libcronet.a مخصوص macOS می‌آید که با with_naive_outbound لینک می‌شود. کتابخانه‌های iOS و tvOS آن را ندارند.

رفع: protocol/naive/h_cronet_info_plist_darwin.go خودش این تابع را تعریف می‌کند و دادهٔ خالی برمی‌گرداند، پس لینکر دیگر info_plist_data.o را از آرشیو برنمی‌دارد. فقط کد چندپردازه‌ای کرومیوم (Mach port rendezvous و بررسی امضای کد پردازه‌های فرزند) آن را صدا می‌زند و Cronet درون همان پردازه اجرا می‌شود، پس naive مثل قبل کار می‌کند.

MASQUE: لینک‌های masque:// از MahsaNG بارگذاری نمی‌شدند

کامیت: در انتظار

چه می‌شد: MahsaNG کلید دستگاه را پیش از @ و endpoint را با پورتش پس از آن می‌نویسد و کلید endpoint را نمی‌گذارد. ray2sing فقط پارامتر key را می‌خواند، پس دستگاه کلید نداشت و کانفیگ با خطای «private_key is not an ECDSA key» رد می‌شد؛ با کلید هم، دستگاه بدون endpoint_public_key پذیرفته نمی‌شد و پورتی جز 443 دور ریخته می‌شد.

رفع: ray2sing این شکل لینک را می‌خواند (کلید، endpoint و پورت، address، mtu، keepalive، transport، sni، insecure، pin، ech، packets). پیش‌فرض endpoint_public_key دستگاه کلید گواهی ثابتی است که لبهٔ MASQUE کلادفلر روی همهٔ endpointها می‌دهد، پورت endpointی که به شکل ip:port نوشته شده استفاده می‌شود و tls.certificate_public_key_sha256 endpoint را با هش کلیدش پین می‌کند، همان‌طور که لینک‌ها می‌خواهند. لینک به WARP همچنان کانفیگ WARP است: وقتی کلادفلر دستگاه لینک را رد کند (کلیدش هرگز ثبت نشده، یا دستگاه حذف شده)، outbound مانند پروفایل دستگاهی ثبت می‌کند، آن را به‌عنوان جایگزین ذخیره می‌کند و تونل را با آن اجرا می‌کند.

DNS و مسیریابی: سایت‌های IPv6-اول از پراکسی بدون IPv6 باز نمی‌شدند

کامیت: bc0447ad8

چه می‌شد: سایت‌هایی که رکورد AAAA دارند (گوگل، یوتیوب، بیشتر CDNهای بزرگ) هر وقت دستگاه IPv6 داشت از راه IPv6 خواسته می‌شدند، از پراکسی‌ای که خروجی‌اش IPv6 ندارد. اتصال در سمت سرور می‌مرد، مرورگر به‌موقع به IPv4 برنمی‌گشت و سایت باز نمی‌شد، در حالی که سایت‌های فقط-IPv4 از همان پراکسی کار می‌کردند. دور زدن IPv6 در مسیریابی هم راه‌حل نبود: ترافیکی که مسیریابی نشود از کنار پراکسی نشت می‌کند.

رفع: پایش هر outbound و endpoint را جداگانه برای دسترسی IPv6 می‌آزماید و پاسخ را در تاریخچهٔ آزمون URL آن نگه می‌دارد. قانون DNS با ipv6_by_outbound تا وقتی آن outbound بدون IPv6 است پاسخ AAAA نمی‌دهد و قانون مسیریابی با ipv6_unavailable_via مقصدهای IPv6ای را که آنجا می‌میرند می‌گیرد تا بی‌درنگ رد شوند. یک پراکسی می‌تواند IPv6 داشته باشد و بعدی نداشته باشد؛ پراکسی‌ای که IPv6 پیدا کند از دور بعدی برای آن به کار می‌رود. نگاه کنید به IPv6 بر پایهٔ outbound.

DNS: سرور معلق در گروه ترتیبی همهٔ پرسش‌ها را ناموفق می‌کرد

کامیت: 900d60c40

چه می‌شد: گروه ترتیبی در هر پرسش از سرور اول شروع می‌کرد و به هر سرور همهٔ زمان باقی‌ماندهٔ پرسش را می‌داد. سرور اولی که پورتش را خروجی پراکسی می‌انداخت تا پایان مهلت DNS معلق می‌ماند، پرسش ناموفق می‌شد و سرورهای بعد از آن هرگز پرسیده نمی‌شدند. با گروه remote هسته که سرور TCP انتخاب‌شده را اول می‌گذارد، کانفیگ‌های عبوری از چنین پراکسی‌ای هیچ نامی را حل نمی‌کردند، در حالی که همان کانفیگ‌ها با نشانی UDP ساده کار می‌کردند.

رفع: سروری که شکست بخورد تا error_ttl بعد از بقیه پرسیده می‌شود، وقتی همه شکست بخورند باز هم پرسیده می‌شود و با تغییر شبکه فراموش می‌شود. برای هر سرور حداکثر member_timeout صبر می‌شود، یا اگر تنظیم نشده باشد، زمان باقی‌ماندهٔ پرسش میان سرورهای پرسیده‌نشده، دست‌کم ۲ ثانیه برای هر یک.

پایش: پینگ پروکسی انتخاب‌شده دیر می‌رسید

کامیت: f7d6695cb

چه می‌شد: دورهٔ اول تست، اعضا را به ترتیب سریع‌ترین نتیجهٔ اجرای قبل می‌گرفت، پس پروکسی‌ای که کاربر انتخاب کرده بود پشت همهٔ اعضای پیش از خودش می‌ماند. جابه‌جایی selector به عضوی دیگر فقط خروجی‌هایی را تست می‌کرد که از طریق selector مسیر می‌شدند، نه خود انتخاب جدید را، پس پینگش تا دورهٔ بعد می‌ماند. تست درخواستی (یک لمس، یک انتخاب) از صف workerها می‌گذشت، پشت دوره‌ای پر از timeout که هر یک از workers را تا url_test_timeout نگه می‌دارد؛ تأخیرش فقط پس از جست‌وجوی IP که به دنبال تست URL می‌آید و همان‌قدر طول می‌کشد ذخیره می‌شد؛ و پروکسی‌ای که پاسخ نمی‌داد تا پایان کل مهلت هیچ چیز نشان نمی‌داد.

رفع: پیش از دورهٔ اول، خروجی‌ای که انتخاب هر گروه به آن می‌رسد (انتخاب selector، عضو متعادل‌کننده، از میان گروه‌های تودرتو) پیش از همه تست می‌شود و تغییر انتخاب، انتخاب جدید را با اولویت تست می‌کند. تست درخواستی همان لحظه، کنار تست‌های دوره، و مرحله‌ای اجرا می‌شود (on_demand_timeouts، ۱، ۳ و ۱۰ ثانیه اگر تنظیم نشده باشد): پروکسی در مرحلهٔ اول بدون پاسخ، یا به محض ناموفق شدن تلاش اول، فعلاً «قطع» نشان داده می‌شود و اولین پاسخ از هر تلاشی بی‌درنگ جایش را می‌گیرد، هرچقدر هم دیر. گروه‌ها در این میان آخرین نتیجهٔ قطعی را نگه می‌دارند. تأخیر به محض پاسخ تست URL منتشر می‌شود، اطلاعات IP وقتی جست‌وجویش تمام شد، و نتیجهٔ اولویت‌دار بدون انتظار debounce_window به مشترکان گروه‌ها می‌رسد.

پایش: پس از مدتی دیگر هیچ چیز تست نمی‌شد

کامیت: f7d6695cb

چه می‌شد: تست URL خروجی را با مهلتی در context شماره‌گیری می‌کند. خروجی‌ای که شماره‌گیری‌اش آن مهلت را نادیده می‌گیرد، چنان‌که شماره‌گیری به یک کتابخانهٔ بومی ممکن است (شماره‌گیری naive با cronet، بنا بر لاگ‌ها)، هرگز برنمی‌گشت؛ پس worker آن برای همیشه منتظر می‌ماند و دوره‌ای هم که منتظر آن نتیجه بود. چند تست از این دست کافی بود تا همهٔ workerها گرفتار شوند: نه دوره‌ای، نه تست اولویت‌داری، نه پینگی برای هیچ خروجی، تا راه‌اندازی دوبارهٔ هسته.

رفع: هر worker برای یک تست حداکثر دو برابر مهلت آن به‌علاوهٔ url_test_timeout و ۲ ثانیه منتظر می‌ماند: تست URL، جست‌وجوی IP و یک تست دیگر پس از بازنشانی شبکه، هر یک با مهلت خودش. پس از آن، تست ناموفق حساب می‌شود، نتیجهٔ دیرهنگامش اگر برسد دور ریخته می‌شود و worker به تست بعدی می‌رود.

امنیتی: همهٔ inboundها روی MPTCP گوش می‌دادند

کامیت: 9aca6c8c1

چه اتفاقی می‌افتاد: Go نسخهٔ ۱.۲۴ به بعد روی لینوکس به‌طور پیش‌فرض روی Multipath TCP گوش می‌دهد، و کرنل لینوکس اجازه می‌داد یک root راه دور روی MPTCP نسخهٔ IPv4 گوش بدهد. listener و dialer فقط وقتی tcp_multi_path درست بود MPTCP را روشن می‌کردند و در غیر این صورت پیش‌فرض Go را می‌گذاشتند، پس هر inbound در بیلدی که تنظیم multipathtcp=0 را نداشت یک listener از نوع MPTCP بود. باینری sing-box این تنظیم را در فلگ‌های لینک داشت؛ Makefile هسته (hiddify-core) هیچ‌کدام را نداشت و CI آن سمبلی را تنظیم می‌کرد که وجود ندارد (internal/godebug.defaultGODEBUG)، پس هسته‌های اپ روی MPTCP گوش می‌دادند.

رفع: listener و dialer مگر وقتی tcp_multi_path بخواهد MPTCP را خاموش می‌کنند، و هر دو ماژول اصلی godebug multipathtcp=0 را در go.mod دارند که هر بیلدی از آن‌ها بدون فلگ لینک می‌گیرد، چنان‌که go version -m باینری‌ها نشان می‌دهد. یک تست مخصوص لینوکس بررسی می‌کند که inbound با تنظیمات پیش‌فرض یک اتصال TCP ساده می‌پذیرد، نه MPTCP. کرنل لینوکس را هم به‌روز کنید: این تغییر اشکال کرنل را رفع نمی‌کند، و inboundای که روی همهٔ نشانی‌ها (0.0.0.0) گوش می‌دهد در هر حال از شبکه در دسترس است.

بالانسر: هر دور تست انتخاب را جابه‌جا می‌کرد و همهٔ اتصال‌ها را می‌برید

کامیت: d23f1250b

چه اتفاقی می‌افتاد: راهبرد lowest-delay با هر به‌روزرسانی پایش کمینهٔ تازه را می‌گرفت، بدون هیچ تلرانسی (گزینه پذیرفته ولی نادیده گرفته می‌شد)، و بالانسر سپس همهٔ اتصال‌های خود را می‌بست، با interrupt_exist_connections اتصال‌های inbound را هم. دو سرور با تأخیر نزدیک به هم هر دور جابه‌جا می‌شدند، و هنگام پینگ گرفتن از همهٔ سرورها نتایج یکی‌یکی می‌رسید، پس هر نیم ثانیه انتخاب عوض و اتصال‌ها دوباره بسته می‌شد. تماس صوتی یا ویدیو قطع می‌شد و دانلود به‌روزرسانی برنامه‌ها شکست می‌خورد، در حالی که سرور قبلی مشکلی نداشت. دورهٔ اول هم اعضای تست‌نشده (تأخیر ۰) را جلوتر از اعضای کش‌شده مرتب می‌کرد، پس عضوی که در اجرای قبل سریع‌ترین بود آخر تست می‌شد و گروه به‌جای بهترینِ کش، از عضو اول فهرست شروع می‌کرد.

رفع: lowest-delay عضوش را تا وقتی پاسخ می‌دهد نگه می‌دارد و فقط به عضوی می‌رود که بیش از tolerance (۵۰ میلی‌ثانیه اگر تنظیم نشده) سریع‌تر باشد، یا وقتی عضوش در تست شکست بخورد. اتصال‌ها به تفکیک عضو ردیابی می‌شوند؛ عضوی که سریع‌تر شده فقط اتصال‌های تازه را می‌گیرد، عضوی که می‌میرد فقط اتصال‌های خودش بسته می‌شود، و ۳۰ ثانیه پس از شروع، وقتی نتیجه‌ای از همین اجرا باشد، اتصال‌های اعضایی که گروه دیگر استفاده نمی‌کند یک بار جابه‌جا می‌شوند. بالانسر از نتایج کش‌شده شروع می‌کند و دورهٔ اول، سریع‌ترین عضو کش را اول تست می‌کند. با تست‌های واحد راهبرد، قطع به تفکیک عضو، جابه‌جایی یک‌باره، شروع از کش و ترتیب دوره آزموده شد.

نویز وایرگارد: بایت‌های reserved روی بسته‌های نویز نوشته می‌شد

کامیت: wireguard-go@3b861c636

چه اتفاقی می‌افتاد: endpoint از نوع wireguard بدون detour از bind استاندارد می‌فرستد و SendWithoutModify آن همان Send را صدا می‌زد؛ Send بایت‌های reserved همتا (شناسهٔ کلاینت WARP) را در بایت‌های ۱ تا ۳ هر بسته می‌نویسد. بسته‌های نویز با SendWithoutModify فرستاده می‌شوند تا دست‌نخورده بمانند، اما reserved روی آن‌ها هم نوشته می‌شد: قالب QUIC یا Initial ساختهٔ <qi> به‌جای ce 00 00 00 01 08 … به شکل ce 57 3d 35 01 08 … با نسخهٔ QUIC خراب می‌رفت، و همهٔ بسته‌های تصادفی همان سه بایت بسته‌های خود تونل را داشتند که اثرانگشت ساده‌ای است. endpointهای AmneziaWG (awg) و endpointهای پشت detour مشکلی نداشتند.

رفع: bind استاندارد بایت‌های reserved را فقط در Send می‌نویسد. با ضبط بسته‌ها برای کانفیگ‌های WARP با لینک wnoise، گزینهٔ amnezia-wg-option کلش و jc/id/ip/ib در sing-box آزموده شد: قالب‌ها بایت به بایت فرستاده می‌شوند و تونل‌ها همچنان وصل می‌شوند.

XHTTP: سرور goroutineها و اتصال‌ها را نگه می‌داشت

کامیت: 0f0f6966e

چه اتفاقی می‌افتاد: در inbound ‏XHTTP، درخواست دانلود (GET) اتصالش را به inbound می‌دهد که سرآیند درخواست را از صف آپلود می‌خواند. فقط بستن اتصال این خواندن را تمام می‌کند و اتصال تنها پس از بازگشت inbound بسته می‌شد؛ پاک‌سازی نشست هم پس از شروع دانلود خاموش است. کلاینتی که پیش از هر آپلودی می‌رفت، handler، اتصال و نشستش را برای همیشه نگه می‌داشت: سرورها در یک هفته به حدود ۱۱٬۰۰۰ goroutine و ۱٬۵۰۰ سوکت رسیدند (hiddify-core#163). صف آپلود پری که کسی آن را نمی‌خواند هم می‌توانست پاک‌سازی نشست را برای همیشه متوقف کند.

رفع: اتصال همین که درخواست دانلود تمام شود بسته می‌شود، و آپلود در انتظار با بسته شدن صف کنار می‌کشد. با کلاینتی که پیش از آپلود می‌رود و با صف پر تست شد، که هر دو بدون این رفع گیر می‌کردند، و در برابر Xray 26.10.29 و 26.3.27.

AmneziaWG: endpointی که detourش آماده نبود بالا نمی‌آمد

کامیت: df246df56

چه اتفاقی می‌افتاد: endpoint ‏awg با یک peer، هنگام شروع اتصالش به peer را باز می‌کند. وقتی detour آن هنوز آماده نبود، مثلاً endpoint ‏WireGuard دیگری که هنوز در حال شروع بود، این اتصال شکست می‌خورد و با آن شروع endpoint و کل پیکربندی («WireGuard is not ready yet»). حالت awg در endpoint ‏warp دوباره تلاش می‌کرد؛ endpoint ‏awg معمولی نه.

رفع: endpoint بدون این اتصال شروع می‌شود و هر ثانیه دوباره آن را می‌سازد تا detour اجازه دهد، و از آن پس مثل قبل کار می‌کند. با dialerی تست شد که اولین تلاش‌هایش شکست می‌خورد و بدون این رفع شروع را از کار می‌انداخت، و به‌صورت زنده با یک endpoint ‏awg از راه یک endpoint ‏wireguard ‏WARP.

پایش: خروجیِ پشت detour پیش از detourش تست می‌شد

کامیت: 79c0e9c00

چه اتفاقی می‌افتاد: خروجی‌ای که از طریق خروجی دیگری شماره‌گیری می‌کند، همین که آن خروجی فقط وصل بود تست می‌شد؛ detourی که selector است وقتی پذیرفته می‌شد که یکی از اعضا پینگ داشت، حتی پینگ کش‌شده، یا همهٔ اعضا وصل بودند، که selector روی یک پراکسی SOCKS همیشه هست. پس هاپ امنیت اضافه (سایفون، WARP) پیش از آنکه پراکسیِ مسیرش نتیجه‌ای داشته باشد تست می‌شد و پینگ و IPاش چیزی نمی‌سنجید. این بررسی پس از دهمین دورهٔ تست هم دیگر اعمال نمی‌شد.

رفع: وقتی تست می‌شود که خروجی‌ای که detourش به آن می‌رسد (برای گروه، عضو انتخاب‌شده) در این اجرا نتیجه داشته باشد و در حال تست دوباره نباشد، و حداکثر ۱۰ بار ۳ ثانیه صبر می‌کند. وقتی آن خروجی پس از ناموفق بودن کار کند، خروجی‌هایی که از طریقش تست شده‌اند دوباره تست می‌شوند. با خروجی‌ای پشت یک selector آزموده شد که بدون این رفع زودتر از موعد تست می‌شد.

پایش: IP کش‌شده هیچ‌وقت دوباره جست‌وجو نمی‌شد

کامیت: 9b7726a00

چه اتفاقی می‌افتاد: در شروع، هر خروجی IP خروجیِ اجرای قبل را از فایل کش نشان می‌دهد. این IP فقط تا وقتی نتیجهٔ خروجی از کش بود دوباره جست‌وجو می‌شد، و نخستین تست اجرا، حتی ناموفق، این را پاک می‌کرد. پس IP کش‌شده تا پایان اجرا می‌ماند، هرچند IP خروجی WARP یا سایفون ممکن است بین اجراها عوض شود.

رفع: ‏URLTestHistory.IpInfoFromCache اطلاعات IP کش‌شده را علامت می‌زند و هر تست موفق آن را جست‌وجو می‌کند تا یک جست‌وجوی این اجرا موفق شود. برنامه می‌تواند IP کش‌شده را تأییدنشده نشان دهد. با یک IP ذخیره‌شده، یک تست ناموفق و سپس یک تست موفق آزموده شد که بدون این رفع IP قدیمی را نگه می‌داشت.

پایش: تست URL برای خروجیِ در حال اتصال کاری نمی‌کرد

کامیت: 8239116da

چه اتفاقی می‌افتاد: تست URLی که وقتی خروجی هنوز در حال اتصال بود (سایفون) خواسته می‌شد رد و دوباره در صف گذاشته می‌شد، اما خروجی همچنان «تست در صف» علامت داشت، پس آن تلاش دوباره و هر تست URL بعدیِ آن تا راه‌اندازی دوبارهٔ هسته کنار گذاشته می‌شد.

رفع: تلاش دوباره اول این علامت را پاک می‌کند. با تست URLی که هنگام اتصال خروجی خواسته شد و سپس تستی دیگر آزموده شد، که بدون این رفع هیچ‌وقت اجرا نمی‌شد.

‏Psiphon: هیچ‌وقت وصل نمی‌شد، «domain not resolved»

کامیت: 1a9f0b4f7

چه اتفاقی می‌افتاد: ‏Psiphon (تنها، یا در حالت‌های زنجیره) روی هیچ سکویی وصل نمی‌شد. از وقتی اتصال‌های خودِ آن از یک پل SOCKS روی dialerِ خروجی می‌گذرند (c82d7d86b)، آن dialer دامنه‌ها را resolve نمی‌کرد. ‏Psiphon در نخستین اجرا هیچ سروری ندارد و فهرست سرورهایش را از s3.amazonaws.com می‌گیرد؛ هر بار با «domain not resolved» شکست می‌خورد، پس هیچ سروری پیدا نمی‌کرد («no active tunnels»).

رفع: ‏dialer دامنه‌ها را resolve می‌کند، یا آن‌ها را به detour می‌سپارد. ‏Psiphon اکنون در چند ثانیه وصل می‌شود. آزموده شد که dialerِ خروجی دامنه‌ها را resolve می‌کند، کاری که بدون این رفع نمی‌کرد.

‏MASQUE: ‏MASQUE درون MASQUE هیچ‌وقت وصل نمی‌شد

کامیت: 11a8c9737

چه اتفاقی می‌افتاد: وقتی یک خروجی MASQUE پشت خروجی MASQUE دیگری است، تونل درونی endpointِ HTTP/2 خود را با نشانی IPv4 به شکل IPv6 (‏[::ffff:162.159.198.2]:443) شماره‌گیری می‌کند. تونل بیرونی آن را IPv6 می‌دانست و چون فقط نشانی IPv4 داشت، با «missing IPv6 local address» ردش می‌کرد.

رفع: پشتهٔ تونل پیش از انتخاب خانوادهٔ نشانی، مقصد را به IPv4 برمی‌گرداند. با تونلی فقط IPv4 که نشانی IPv4 به شکل IPv6 را شماره‌گیری می‌کند آزموده شد؛ بدون این رفع آن را رد می‌کرد.

‏ترابرد HTTP (‏HTTP/1.1): ‏Trojan پشت سرآیند HTTP خامِ Xray پاسخ 400 می‌گرفت

کامیت: e29388e76

چه اتفاقی می‌افتاد: خروجی Trojan با ترابرد http روی HTTP/1.1 (‏«raw TCP با سرآیند HTTP» در Xray، یا rawhttp در Hiddify Manager) در برابر سرور Xray پشت HAProxy حدود نیمی از مواقع با خطای «v2ray-http: unexpected status: 400 Bad Request» شکست می‌خورد. با پروتکل PROXY، نخستین خواندنِ Xray با این سرآیند حداکثر ۲۵۶ بایت است، یعنی ۲۰۰ تا ۲۲۸ بایت از درخواست. ‏Xray سرآیند HTTP را از همین قطعه تجزیه می‌کند و اگر قطعه وسط نام یک سرآیند تمام شود خطا می‌دهد؛ و اگر کل درخواست در آن جا شود، ورودی Trojanِ آن درخواستش را فقط از باقیماندهٔ همین قطعه می‌خواند و قطعهٔ کوتاه‌تر از سرآیند ۵۸ بایتی‌اش را رد می‌کند. سرآیندها با ترتیب تصادفی فرستاده می‌شوند، پس درخواست کوتاه هیدیفای بسته به اتصال در یکی از این دو حالت می‌افتاد.

رفع: درخواست با یک سرآیند Cookie شروع می‌شود (مگر پیکربندی خودش آن را داشته باشد) که مقدارش بایت‌هایی را که نخستین خواندن ممکن است در آن‌ها تمام شود می‌پوشاند؛ پس درخواست از آن بلندتر است و خواندن همیشه وسط یک مقدار تمام می‌شود. با درخواستی کوتاه آزمایش شد که بدون این رفع در نخستین خواندن جا می‌شود.

‏AmneziaWG: هسته پشت detourی که گیر کرده بود متوقف نمی‌شد

کامیت: 96652cba6

چه اتفاقی می‌افتاد: وقتی WARP در حالت AmneziaWG پشت پراکسی‌ای بود (اینجا VMess روی XHTTP) که جریانش گیر کرده بود، توقف هسته چند دقیقه طول می‌کشید و تمام نمی‌شد. هر ارسال AmneziaWG هنگام نوشتن روی detour قفل شبکهٔ دستگاه را نگه می‌دارد؛ نوشتن هرگز برنمی‌گشت، و بستن دستگاه، که برای بستن سوکت همین قفل را می‌گیرد، منتظرش می‌ماند. جست‌وجوی WARP برای پورتی که پاسخ می‌دهد هم هنگام جایگزین کردن تونل به همین شکل گیر می‌کرد.

رفع: دستگاه پیش از بسته شدن اتصال‌های detourش را می‌بندد، پس نوشتنِ گیرکرده خطا می‌دهد و قفل آزاد می‌شود. با detourی که نوشتن‌هایش گیر می‌کند آزمایش شد؛ بدون این رفع شکست می‌خورد.

‏WARP پشت URL test یا balancer: با هر جابه‌جایی قطع می‌شد

کامیت: ce1ed43e8

چه اتفاقی می‌افتاد: وقتی detour در WARP یک URL test یا balancer بود، هر جابه‌جایی گروه تونل را می‌بست و تونل تازه‌ای باز می‌کرد، درست مثل عوض کردن selector. گروهی که بین دو عضو نزدیک به هم جابه‌جا می‌شود (عضو کم‌تأخیرتر هر چند تست یک بار عوض می‌شود) هر بار WARP را قطع می‌کرد، و در حالت auto هر بار مسابقهٔ تازه‌ای شروع می‌کرد، با اینکه تونل قبلی هنوز کار می‌کرد.

رفع: جابه‌جایی‌ای که کاربر در selector انجام دهد هنوز فوراً اعمال می‌شود. جابه‌جایی‌ای که گروه خودش انجام دهد حداکثر هر ۳۰ ثانیه اعمال می‌شود (و فوراً وقتی عضوی که ترک شده شکست خورده بود یا WARP وصل نیست)، با تونلی تازه که کنار تونل فعلی باز می‌شود و فقط پس از وصل شدن جای آن را می‌گیرد. برای همهٔ تونل‌ها به‌صورت زنده با URL testی که عضوش از کار می‌افتد آزمایش شد.

‏URL test و balancer: جابه‌جایی‌شان دیر به monitoring می‌رسید

کامیت: 4b866b5d9

چه اتفاقی می‌افتاد: URL testی که به عضو دیگری می‌رفت به monitoring خبر نمی‌داد، پس هر چه گروه را دنبال می‌کرد (تونلی که از آن می‌گذرد، فهرست پراکسی‌های برنامه) جابه‌جایی را فقط در تست بعدی monitoring می‌دید. balancer فقط به مشترکان خودش خبر می‌داد، و دنبال‌کنندهٔ همهٔ گروه‌ها می‌توانست اول عضو قبلی را بخواند.

رفع: هر دو هنگام جابه‌جایی به monitoring خبر می‌دهند، همان‌طور که selector می‌دهد. با تستی آزمایش شد که بدون این رفع شکست می‌خورد.

‏حالت auto در WARP: روی تونلی که هرگز وصل نمی‌شد می‌ماند

کامیت‌ها: ae3ec6c67، 8db9e7ba2

مشکل: پشت Extra Security، ‏WARP در حالت auto برای همیشه WARP · AWG ⏳ Connecting... نشان می‌داد. مسابقه تونلی را برمی‌داشت که اول به دست‌دادن پاسخ داده بود، این‌جا AmneziaWG، و آن را نگه می‌داشت هرچند خود تونل هرگز راه نمی‌افتاد: سوکت AmneziaWG از detour، ‏UDP به هر نشانی (0.0.0.0) می‌خواست، که پراکسی‌ای که UDPاش مقصد لازم دارد رد می‌کند (400 Bad Request)، در حالی که probe، ‏UDP به peer می‌خواست و عبور می‌کرد. عوض کردن پراکسی در selector فقط همان تونل را دوباره وصل می‌کرد.

راه‌حل: با یک peer، ‏AmneziaWG، ‏UDP خود را به همان peer باز می‌کند، مثل WireGuard. حالت auto به تونل انتخاب‌شده ۲۰ ثانیه فرصت اتصال می‌دهد و وگرنه بدون آن دوباره مسابقه می‌دهد، و وقتی detour به پراکسی دیگری برود هم دوباره مسابقه می‌دهد (پراکسی بدون UDP آن‌وقت MASQUE روی HTTP/2 می‌گیرد). به‌صورت زنده از طریق یک پراکسی HTTP برای همهٔ تونل‌ها، و با رفتن به SOCKS4 که UDP ندارد، آزموده شد.

‏Psiphon: پس از توقف هستهٔ تونل از کار می‌افتاد، و فقط یکی در هر زمان

کامیت: 3c5e93945

مشکل: وقتی هستهٔ تونل سایفون تسلیم می‌شد (پس از establish_tunnel_timeout، مثلاً در شبکه‌ای که سایفون مدتی مسدود بود) یا خطا می‌داد، خروجی تا راه‌اندازی دوبارهٔ اپ از کار افتاده می‌ماند. خروجی سایفون دوم کنار اولی راه نمی‌افتاد، و دو خروجی در فایل‌های یکسان فهرست سرور می‌نوشتند.

راه‌حل: هستهٔ تونلی که متوقف شده پس از ۵ ثانیه و تا هر ۵ دقیقه دوباره راه می‌افتد. چند خروجی سایفون هم‌زمان کار می‌کنند: هر کدام پوشهٔ دادهٔ خودش را دارد و پیش از این‌که متصل نشان داده شود تونل خودش را بررسی می‌کند، چون اعلان‌های هستهٔ تونل نمی‌گویند دربارهٔ کدام خروجی‌اند.

‏Psiphon: با تونلی که کار می‌کرد در حال اتصال نشان داده می‌شد

کامیت: df13d433d

مشکل: ‏Psiphon «Connecting...» نشان می‌داد در حالی که ترافیک و تست‌های URL از آن عبور می‌کرد. وضعیت اتصال آن فقط از اعلان‌های Tunnels می‌آمد، از طریق تنها نویسندهٔ اعلانی که psiphon-tunnel-core برای کل فرایند دارد. هر Psiphon نویسندهٔ خودش را می‌گذاشت و هنگام بسته شدن آن را بازنشانی می‌کرد، پس Psiphonی که هم‌زمان با بسته شدن دیگری شروع می‌شد (بارگذاری دوبارهٔ اپ) نویسنده را از دست می‌داد و هرگز اعلانش را نمی‌دید؛ دومی اصلاً نمی‌توانست نویسنده‌ای بگذارد.

راه‌حل: یک نویسندهٔ اعلان برای کل فرایند نصب می‌شود و هر اعلان را به همهٔ Psiphonهای در حال اجرا می‌دهد؛ فقط وقتی آخرین آن‌ها متوقف شود بازنشانی می‌شود. اتصال از طریق تونل هم حساب می‌شود: کنترل‌کنندهٔ Psiphon فقط وقتی تونلی برقرار است اتصال می‌سازد، و خطای «no active tunnels» آن را متصل‌نشده علامت می‌زند.

‏اسکنر WARP: ‏probeها همان چیزی را که دستگاه می‌فرستد نمی‌فرستادند

کامیت: 28ff47e9b

مشکل: اسکنر یک endpoint را با دست‌دادنی می‌سنجد که پس از همان noise دستگاه فرستاده می‌شود. ولی در حالت پیش‌فرض noise، ‏probe سرآیند کوتاهی را که دستگاه پیش از هر بستهٔ جعلی می‌گذارد نمی‌گذاشت، و با awg به جای بسته‌های junk و ویژهٔ AmneziaWG همان noise در WireGuard را می‌فرستاد. در نتیجه یک endpoint ممکن بود به probe پاسخ بدهد و به دستگاه نه، یا برعکس.

راه‌حل: دستگاه و probe بسته‌های جعلی را با یک کد می‌سازند و با awg ‏probe بسته‌های i1 تا i5 و بسته‌های junk به تعداد jc را می‌فرستد. با همهٔ پیش‌تنظیم‌های noise در برابر Cloudflare آزموده شد.

‏Shadowsocks: پروتکل‌های QUIC از طریق آن هرگز وصل نمی‌شدند

کامیت: 1e2ddd7fd

مشکل: ‏Hysteria2 و هر پروتکل QUIC دیگری از طریق detour از نوع Shadowsocks هرگز وصل نمی‌شد. ‏QUIC هر بسته را در بافری به اندازهٔ حدود یک بسته می‌خواند، کلاینت Shadowsocks در همان بافر رمزگشایی می‌کند و بستهٔ کامل همراه با salt و tag در آن جا نمی‌شد. نتیجه خطای «cipher: message authentication failed» بود و اتصال QUIC بسته می‌شد.

راه‌حل: کلاینت Shadowsocks هر بسته را کامل می‌خواند و بعد کپی می‌کند. با Hysteria2 از طریق Shadowsocks، ‏VLESS و Trojan آزموده شد؛ پیش از این فقط Shadowsocks شکست می‌خورد.

‏WARP: کار پس‌زمینه بعد از بسته شدن box ادامه می‌یافت و AmneziaWG از طریق detourی که هنوز کار نمی‌کرد هرگز راه نمی‌افتاد

کامیت: 79d187b08

مشکل: box محدودهٔ (scope) هر endpoint را می‌بندد، نه خود endpoint را، پس چرخش پورت، جایگزینی خودکار و goroutineهای آن‌ها در WARP بعد از بسته شدن box ادامه می‌یافتند و می‌کوشیدند جایگزین‌هایی راه بیندازند که با «context canceled» شکست می‌خوردند. جدا از این، AmneziaWG هنگام شروع سوکتش را باز می‌کند و وقتی detour هنوز کار نمی‌کند (گروهی که هنوز روی عضو مرده است) شکست می‌خورد؛ WARP آن‌وقت دست می‌کشید و برای همیشه در حالت «endpoint not initialized» می‌ماند، حتی بعد از اینکه detour درست می‌شد.

راه‌حل: کار پس‌زمینهٔ WARP با بسته شدن محدوده‌اش متوقف می‌شود. اولین راه‌اندازی تونل، مانند ثبت حساب، با فاصلهٔ فزاینده (۱ ثانیه، هر بار دو برابر، تا ۳۰ ثانیه) دوباره امتحان می‌شود. به‌صورت زنده از طریق selectorی که از عضو مرده به عضو سالم می‌رود آزموده شد، و با یک تست واحد که بدون این رفع شکست می‌خورد.

‏MASQUE: تونلی که هنگام بسته شدن outbound راه می‌افتاد باز می‌ماند

کامیت: ff42383d6

مشکل: بستن outbound ‏masque وقتی تونلش هنوز در حال راه‌اندازی بود «tunnel not initialized» برمی‌گرداند، و تونل بعد از راه افتادن هرگز بسته نمی‌شد. نشانی‌های endpoint پروفایل هم با جابه‌جایی‌های ثابت از ip:port بریده می‌شدند، که با هر پورت دیگری خراب می‌شود.

راه‌حل: Close ثبتِ در جریان را پایان می‌دهد، و تونلی که در این میان راه بیفتد وقتی راه‌اندازی‌اش تمام شود بسته می‌شود. endpointها به صورت نشانی همراه پورت تجزیه می‌شوند.

‏masque-client: پیش از رسیدن به سرور متصل نشان داده می‌شد

کامیت: 85cff7f36

مشکل: endpoint ‏masque-client از sing-box اصلی از وضعیت آمادهٔ پیش‌فرض استفاده می‌کرد، پس وقتی هیچ نشستی با سرور نداشت متصل نشان داده می‌شد.

راه‌حل: فقط وقتی آماده است که نشستی با سرور برقرار باشد. endpoint ‏on_demand، که هنگام استفاده وصل می‌شود، همیشه آماده است.

‏Psiphon: وضعیت اتصال دچار race بود و یک notice می‌توانست پروسه را از کار بیندازد

کامیت: 7a85e7be9

مشکل: پرچم اتصال برای هر notice از یک goroutine تازه نوشته و بدون قفل خوانده می‌شد. فقط تنظیم می‌شد و وقتی تعداد تونل‌ها به صفر می‌رسید هرگز پاک نمی‌شد. یک notice ‏Tunnels که تعدادش عدد JSON نبود، روی یک type assertion باعث panic می‌شد.

راه‌حل: noticeها به ترتیب پردازش می‌شوند، وضعیت atomic است و از تعداد تونل‌ها پیروی می‌کند، و تعدادی از هر نوع دیگر نادیده گرفته می‌شود.

پایش: انتخاب دیر به اپ می‌رسید و outboundهای زنجیره‌ای زودتر از موعد آزموده می‌شدند

کامیت: af68dde0a

مشکل: تغییر انتخاب، پیش از آنکه مشترکان گروه آن را ببینند، منتظر debounceی می‌ماند که برای رگبار نتایج آزمون در نظر گرفته شده بود، و مشترکی که سرگرم به‌روزرسانی قبلی بود می‌توانست آن را از دست بدهد. outboundی که پشت detour گروهی بود پیش از آنکه هیچ عضوی از آن گروه کار کند آزموده می‌شد، پس WARP زنجیره‌ای در شروع مرده علامت می‌خورد.

راه‌حل: انتخاب بی‌درنگ، به گروه، والدهایش و گروه فراگیر منتشر می‌شود، با جا برای ۸ رویداد در انتظار. outboundی که detourش آماده نیست، در ۱۰ دور اول، هنوز آزموده نمی‌شود.

‏Sudoku: انتقال بزرگ به خواننده‌ای کند، جریان را reset می‌کرد

کامیت: 16096efa2

مشکل: mux ‏Sudoku کنترل جریان نداشت. وقتی یک جریان ۴ مگابایت داده‌ای را که خواننده‌اش نخوانده بود نگه می‌داشت، فریم بعدی جریان را با «mux receive queue full» ریست می‌کرد. تست خود mux در ۶۱ اجرا از ۹۰۰ اجرا شکست می‌خورد.

راه‌حل: mux منتظر می‌ماند تا خواننده جا باز کند، که نشست و در نتیجه فرستنده را نگه می‌دارد. هر ۹۰۰ اجرا از ۹۰۰ موفق‌اند.

‏MASQUE: ‏HTTP/3 هرگز از طریق Shadowsocks، VLESS یا Trojan وصل نمی‌شد

کامیت: 1f44631b9

مشکل: WARP روی MASQUE با HTTP/3 فقط از طریق detour ‏SOCKS کار می‌کرد. QUIC هر بسته را در بافری به اندازهٔ تقریباً یک بسته می‌خواند، ولی پروکسی‌ای که در جا رمزگشایی می‌کند به جا برای سرآیند و tag خودش هم نیاز دارد. بستهٔ کامل بریده می‌شد، رمزگشایی‌اش شکست می‌خورد («cipher: message authentication failed») و خطای خواندن اتصال QUIC را پایان می‌داد.

راه‌حل: هر بسته به‌طور کامل از اتصال بسته‌ای پروکسی خوانده و سپس کپی می‌شود، در تونل و در probeهای اسکنر. به‌صورت زنده از طریق SOCKS، Shadowsocks، VLESS و Trojan آزموده شد.

‏Psiphon: هر باینری که با Go 1.27 ساخته می‌شد در شروع از کار می‌افتاد

کامیت: bb18926a9

مشکل: Go 1.27 یک فیلد به tls.ConnectionState اضافه کرد. فورک psiphon-tls در شروع بررسی می‌کند که نسخهٔ خودش همان چیدمان را داشته باشد، و panic می‌کرد، پس هر برنامه‌ای که Psiphon داشت پیش از انجام هر کاری از کار می‌افتاد.

راه‌حل: فورک برای هر نسخهٔ Go یک نسخه از struct نگه می‌دارد. با Go 1.27.3 و 1.26.3 ساخته و آزموده شد.

‏Shadowsocks simple-obfs: ‏obfs_host بررسی نمی‌شد

کامیت: 357a0ec54

مشکل: ورودی Shadowsocks فیلد obfs_host را می‌پذیرفت، ولی هرگز آن را با هاستی که کلاینت می‌فرستاد مقایسه نمی‌کرد، پس هر هاستی از دست‌دادن simple-obfs عبور می‌کرد.

راه‌حل: وقتی obfs_host تنظیم شده باشد (یک هاست یا فهرستی جدا شده با کاما)، کلاینتی که Host در HTTP یا نام سرور در TLS آن در فهرست نباشد simple-obfs حساب نمی‌شود و به obfs_fallback فرستاده یا بسته می‌شود. روی obfs از نوع HTTP و TLS، با هاست مجاز و غیرمجاز آزموده شد.

‏DNS: ‏bypass_if_failed در حالت قدیمی پاسخ خالی را نادیده می‌گرفت

کامیت: 0e67074d9

مشکل: در حالت قدیمی DNS، قاعده‌ای با bypass_if_failed فقط وقتی پرس‌وجو خطا می‌داد به قاعدهٔ بعدی می‌رفت. پرس‌وجوی A یا AAAA که بدون هیچ نشانی برمی‌گشت همان‌جا می‌ماند، هرچند lookup همین پاسخ خالی را خطا حساب می‌کرد.

راه‌حل: پرس‌وجوی نشانی که پاسخش هیچ نشانی ندارد حالا خطا حساب می‌شود و به قاعدهٔ بعدی می‌رود، همان‌طور که در lookup.

WARP: ‏WireGuard از طریق detour اغلب هرگز وصل نمی‌شد

کامیت: ce64e6017

مشکل: WARP روی WireGuard پشت یک detour (مثلاً Extra Security) اغلب هرگز وصل نمی‌شد. WireGuard دست‌دادن را از طریق یک ارتباط UDP هم به نشانی IPv4 و هم به نشانی IPv6 پروفایل می‌فرستاد و detourی که IPv6 نداشت آن ارتباط را می‌بست. بدون اسکنر، پورتی از پروفایل که پاسخ نمی‌داد هم برای همیشه استفاده می‌شد. WARP و MASQUE به محض آماده شدن متصل نشان داده می‌شدند، حتی وقتی هیچ ترافیکی عبور نمی‌کرد.

راه‌حل: WARP از نشانی IPv4 ‏peer پروفایل استفاده می‌کند و وقتی پورتی در ۱۰ ثانیه دست‌دادنی کامل نکند به پورت بعدی می‌رود. WARP فقط وقتی متصل حساب می‌شود که آخرین دست‌دادن کمتر از ۳ دقیقه پیش باشد، که با keepalive هر ۲۵ ثانیه تازه می‌ماند؛ MASQUE فقط وقتی اتصالی به سرور دارد. به‌صورت زنده از طریق detour سالم و از کار افتاده، برای WireGuard، AmneziaWG و MASQUE روی HTTP/3 و HTTP/2 آزموده شد.

خروجی قدیمی WireGuard: ‏network نادیده گرفته می‌شد

کامیت: 2ab7b4351

مشکل: خروجی wireguard_legacy فیلد network را می‌پذیرفت، ولی همیشه TCP و UDP را ارائه می‌داد.

راه‌حل: خروجی شبکه‌های فهرست‌شده در network را ارائه می‌دهد، همان‌طور که خروجی قدیمی sing-box اصلی می‌داد. ping همیشه ارائه می‌شود.

Sudoku: ‏fallback کاری نمی‌کرد

کامیت: d53e3c37f

مشکل: ورودی Sudoku فیلد fallback را می‌پذیرفت ولی از آن استفاده نمی‌کرد. اتصالی که Sudoku نبود بسته می‌شد.

راه‌حل: اتصالی که دست‌دادنش شکست بخورد، همراه بایت‌هایی که تا آن لحظه فرستاده، به نشانی fallback سپرده می‌شود، پس کاوشگر سایتِ آن‌جا را می‌بیند. اتصالی که پیش از شکست دست‌دادن بیش از ۶۴ کیلوبایت بفرستد بسته می‌شود.

Call: حالت ناشناختهٔ wbstream هرگز وصل نمی‌شد

کامیت: 7d6d0ef26، 88d4af4c2

مشکل: با "platform": "wbstream"، مقدار mode به جز dc یا video پذیرفته می‌شد، ولی تونل هرگز وصل نمی‌شد.

راه‌حل: کانفیگ با خطای «call: unknown wbstream mode …, use dc or video» بارگذاری نمی‌شود.

Call: قوانین route پس از اتصال دوباره از کار می‌افتادند و اتصال ناموفق تکرار نمی‌شد

کامیت: 3f48fb169

مشکل: در vk، telemost و dion، پس از اتصال دوبارهٔ تماس، طرف سرور اتصال‌ها را خودش و بدون گذر از router باز می‌کرد، پس قوانین route دیگر اعمال نمی‌شدند. inbound و outbound فقط یک بار وصل می‌شدند و پس از شکست، تا راه‌اندازی دوبارهٔ sing-box دوباره تلاش نمی‌کردند. در dion، ‏email و password بدون کوکی با خطای «empty cookie string» شکست می‌خوردند. bridge و bridge فعال تله‌موست بدون قفل از چند goroutine خوانده و نوشته می‌شدند.

راه‌حل: پس از اتصال دوباره، اتصال‌ها باز به router سپرده می‌شوند. هر دو طرف بعد از ۵ ثانیه، ۱۰ ثانیه، ۳۰ ثانیه و سپس هر یک دقیقه دوباره تلاش می‌کنند، تا وقتی sing-box متوقف شود. در dion، email و password به‌تنهایی برای ورود کافی‌اند. bridgeها به شکل امن خوانده و نوشته می‌شوند.

TLS: ‏tls_fragment درون بلوک tls کاری نمی‌کرد

کامیت: a29c81702

مشکل: قالب‌های قدیمی ویرایشگر tls_fragment را درون بلوک tls خروجی می‌گذاشتند. sing-box آن را آن‌جا می‌پذیرفت ولی اعمال نمی‌کرد.

راه‌حل: tls_fragment روشن درون بلوک tls به فیلدهای اتصال خروجی منتقل می‌شود و آن‌جا اعمال می‌شود. اگر فیلدهای اتصال خودشان tls_fragment روشن داشته باشند، همان به کار می‌رود.

WARP MASQUE: تغییر نام خروجی یک دستگاه تازه ثبت می‌کرد

کامیت: a6ca1672a

مشکل: پروفایل MASQUE و امتیازهای اسکنر با tag خروجی ذخیره می‌شدند، پس تغییر نام خروجی یک دستگاه تازه در Cloudflare ثبت می‌کرد. وقتی profile.detour تنظیم شده بود و outbound manager در دسترس نبود، خروجی با اشاره‌گر nil از کار می‌افتاد.

راه‌حل: فیلد تازهٔ unique_identifier نام پروفایل و امتیازهای ذخیره‌شده را تعیین می‌کند. اگر خالی باشد، مثل warp از tag استفاده می‌شود. نبودن outbound manager به‌صورت خطا گزارش می‌شود.

DNSTT: پرسش‌های DNS ممکن بود دوباره به داخل VPN برگردند

کامیت: 8f5156c41

مشکل: کلاینت DNSTT سوکت‌های resolver را خودش باز می‌کند و این سوکت‌ها تنظیم‌های سوکت sing-box را نمی‌گرفتند. bind_interface، routing_mark و محافظت سوکت سیستم‌عامل اعمال نمی‌شدند، پس روی VPN پرسش‌های DNS ممکن بود دوباره به داخل تونل برگردند.

راه‌حل: سوکت‌های resolver تنظیم‌های سوکت خروجی را می‌گیرند. برای detour، که کلاینت DNSTT نمی‌تواند از آن استفاده کند، هشدار در لاگ نوشته می‌شود.

DNSTT: ‏record-type تونل را تغییر نمی‌داد

کامیت: 474f382e5

مشکل: record-type فقط نام فهرست ذخیره‌شدهٔ resolverها را تعیین می‌کرد. تونل همیشه رکورد TXT می‌خواست.

راه‌حل: record-type نوع رکوردی را که سرور داده را با آن برمی‌گرداند انتخاب می‌کند: txt، cname، a، aaaa، mx، ns یا srv. سرور باید از همان نوع استفاده کند.

DNSCrypt: استمپ از نوع دیگر فقط در اولین پرسش شکست می‌خورد

کامیت: 219cd3dcc

مشکل: سرور sdns فقط استمپ DNSCrypt را می‌پذیرد، ولی استمپ DoH، DoT یا DNS ساده بارگذاری می‌شد و فقط در اولین پرسش شکست می‌خورد.

راه‌حل: چنین استمپی هنگام بارگذاری کانفیگ شکست می‌خورد و خطا می‌گوید از نوع سرور https، tls یا udp استفاده کنید.

ورودی SSH: ‏host_key_algorithms، network و deadlineها نادیده گرفته می‌شدند

کامیت: 0ebdf81b7

مشکل: ورودی SSH فیلدهای host_key_algorithms و network را می‌خواند ولی از هیچ‌کدام استفاده نمی‌کرد. اتصال‌های forward شده مهلت‌های خواندن و نوشتن را نادیده می‌گرفتند.

راه‌حل: host_key_algorithms host keyها را محدود می‌کند. کلید RSA فقط با rsa-sha2-512، rsa-sha2-256 یا ssh-rsa که در فهرست آمده باشند امضا می‌کند. کلیدهای دیگر فقط وقتی استفاده می‌شوند که نوعشان در فهرست باشد. اگر هیچ host keyی باقی نماند، کانفیگ بارگذاری نمی‌شود. network بدون udp قابلیت UDP over TCP را خاموش می‌کند. مهلت‌ها روی اتصال‌های forward شده کار می‌کنند.

Balancer: ‏delay_acceptable_ratio خالی هیچ عضو قابل‌استفاده‌ای باقی نمی‌گذاشت

کامیت: 3943213db

مشکل: وقتی delay_acceptable_ratio تنظیم نشده بود، مقدارش 0 بود، پس هیچ عضوی در محدودهٔ تأخیر قابل‌قبول نبود. round-robin فقط از سریع‌ترین عضو استفاده می‌کرد و consistent-hashing و sticky-sessions همهٔ عضوها را مرده می‌شمردند.

راه‌حل: مقدار خالی یا 0 یعنی 2، همان مقداری که برنامهٔ هیدیفای می‌فرستد.

AmneziaWG: اولین بستهٔ پس از تنظیم s4 دور ریخته می‌شد

کامیت: 40dfde705

مشکل: پس از تنظیم s4، اولین بستهٔ داده با padding قبلی فرستاده می‌شد و سرور آن را دور می‌ریخت.

راه‌حل: amneziawg-go همراه برنامه با این اصلاح به‌روز شده است.

گزارش‌ها: آرشیوها دادهٔ حساس داشتند

کامیت: 37d5006d1

مشکل: آرشیوهای گزارش رمزنگاری نمی‌شوند، ولی وقتی برنامه رمزنگاری می‌خواست با پسوند .age نام‌گذاری می‌شدند. این آرشیوها کانفیگ، اتصال‌های باز گزارش‌های OOM و لاگ‌هایی با نشانی‌ها، لینک‌ها و رمزها را در خود داشتند. فرادادهٔ گزارش مسیر کامل پروسه را داشت که ممکن است نام کاربر را در بر بگیرد.

راه‌حل: آرشیوها همیشه پسوند .zip دارند. کانفیگ و اتصال‌های باز در آن‌ها گذاشته نمی‌شوند. از لاگ‌ها و فایل‌های متنی دیگر نشانی‌های IP، لینک‌های پراکسی و استمپ‌های DNS، مسیر و query نشانی‌های URL، UUIDها، ایمیل‌ها و رمزهای key=value پاک می‌شوند. فراداده فقط نام پروسه را نگه می‌دارد.

Call: خرابی relay تله‌موست برنامه را می‌بست و VK توکن پیوستن را در لاگ می‌نوشت

کامیت: e727940f5

مشکل: وقتی relay تله‌موست راه نمی‌افتاد، کل پروسه بسته می‌شد. در vk، پیوستن به تماس موجود توکن پیوستن تماس را، که بخشی از کلید تونل است، در لاگ می‌نوشت.

راه‌حل: خطای تله‌موست در لاگ نوشته می‌شود و پروسه به کار ادامه می‌دهد. توکن پیوستن دیگر در لاگ نوشته نمی‌شود.

WireGuard: بلوک awg بی‌صدا نادیده گرفته می‌شد

کامیت: 3fa692844

مشکل: endpoint ‏wireguard بلوک awg را می‌پذیرفت ولی از آن استفاده نمی‌کرد. endpoint با WireGuard ساده کار می‌کرد، در حالی که کانفیگ پنهان‌شده به نظر می‌رسید.

راه‌حل: چنین کانفیگی بالا نمی‌آید و می‌گوید از "type": "awg" استفاده کنید.

AmneziaWG: ‏s3، s4 و بازه‌های h جلوی endpoint را می‌گرفتند

کامیت: ee5e66490

مشکل: نسخهٔ amneziawg-go همراه برنامه از AmneziaWG 2.0 قدیمی‌تر بود. کانفیگی با s3، s4 یا بازه در h1 تا h4، آن‌طور که سرورهای AmneziaWG 2.0 می‌دهند، با خطای «invalid UAPI device key: s3» بالا نمی‌آمد.

راه‌حل: endpoint از amneziawg-go 3.1 استفاده می‌کند که این مقادیر را می‌پذیرد و تنظیم‌های 3.x را هم اضافه می‌کند.

DNSCrypt: هر پرسش گواهی resolver را دوباره می‌گرفت

کامیت: 3e475bb96

مشکل: سرور sdns پیش از هر پرسش گواهی resolver را می‌گرفت، پس هر جست‌وجو دو رفت‌وبرگشت طول می‌کشید.

راه‌حل: گواهی تا وقتی منقضی شود، پرسشی شکست بخورد یا شبکه عوض شود نگه داشته می‌شود.

XHTTP: حالت auto با کلاینت Xray یکی نبود

کامیت: a4310256e

مشکل: با mode برابر auto، کلاینت همیشه از packet-up استفاده می‌کرد. کلاینت Xray روی REALITY از stream-one استفاده می‌کند، پس سروری که فقط stream-one را می‌پذیرد کلاینت هیدیفای با auto را رد می‌کرد، در حالی که کلاینت Xray وصل می‌شد.

راه‌حل: auto حالت را مثل کلاینت Xray انتخاب می‌کند: روی REALITY ‏stream-one، روی REALITY با download جداگانه stream-up، و در غیر این صورت packet-up. با Xray 26.10.21 و 26.3.27 آزموده شد.

تست URL: تست لغوشدهٔ unified delay ادامه پیدا می‌کرد

کامیت: 48dd50df5

مشکل: با unified delay، هر تست دو درخواست می‌فرستد. درخواست دوم با لغو تست متوقف نمی‌شد و ممکن بود تا ۱۵ ثانیه ادامه پیدا کند. لغو در فاصلهٔ دو درخواست هم موفق گزارش می‌شد.

راه‌حل: درخواست دوم با لغو متوقف می‌شود و تست لغوشده خطا گزارش می‌کند.

SSH: ترافیک UDP به خروجی‌ای فرستاده می‌شد که نمی‌توانست آن را حمل کند

کامیت: a4afbdedf

مشکل: خروجی SSH به‌طور پیش‌فرض UDP را هم می‌پذیرفت، ولی SSH فقط با udp_over_tcp می‌تواند UDP حمل کند. بدون آن، مسیریاب UDP را به این خروجی می‌فرستاد و همهٔ اتصال‌های UDP شکست می‌خوردند.

راه‌حل: خروجی فقط وقتی udp_over_tcp روشن است UDP را می‌پذیرد، پس UDP به خروجی دیگری می‌رود.

SSH: یک درخواست forward خراب کل اتصال را می‌بست

کامیت: ea5c6b5be

مشکل: وقتی ورودی SSH درخواست port-forwardی می‌گرفت که نمی‌توانست بخواند، کل اتصال SSH را با همهٔ forwardهای دیگرش می‌بست.

راه‌حل: فقط همان درخواست رد می‌شود و اتصال و forwardهای دیگرش ادامه می‌یابند.

Balancer: اتصال‌ها در هر دور تست بسته می‌شدند

کامیت: 0e4c6636e

مشکل: با consistent-hashing یا sticky-sessions و interrupt_exist_connections، balancer پس از هر به‌روزرسانی پایش همهٔ اتصال‌هایش را می‌بست، حتی وقتی چیزی تغییر نکرده بود.

راه‌حل: اتصال‌ها فقط وقتی بسته می‌شوند که مجموعهٔ عضوهای قابل‌استفاده تغییر کند.

Rule setها: دانلود ناموفق فقط پس از یک روز دوباره امتحان می‌شد

کامیت: d918f8167

مشکل: rule set راه‌دوری که نسخهٔ کش‌شده ندارد خالی شروع می‌شود و پس از بالا آمدن سرویس دانلود می‌شود. اگر این دانلود شکست می‌خورد، مثلاً چون پراکسی هنوز در حال اتصال بود، تلاش بعدی فقط پس از update_interval انجام می‌شد که به‌طور پیش‌فرض یک روز است. تا آن زمان قاعده‌های آن اعمال نمی‌شدند.

راه‌حل: دانلود ناموفق پس از ۱ ثانیه، ۱۰ ثانیه، ۳۰ ثانیه، ۱ دقیقه، ۵ دقیقه و ۱۰ دقیقه، و سپس هر ۳۰ دقیقه تا موفقیت دوباره امتحان می‌شود. در این مدت نسخهٔ کش‌شده در استفاده می‌ماند.

XHTTP: وقتی session جای دیگری بود، seq در مسیر گم می‌شد

کامیت: a22fe417c

مشکل: سرور XHTTP مسیر را فقط وقتی می‌خواند که هم session_placement و هم seq_placement برابر path بودند. اگر session در query، هدر یا کوکی بود و seq در مسیر، همهٔ درخواست‌های packet-up بدون seq می‌ماندند و آپلود کار نمی‌کرد.

راه‌حل: مثل Xray، هر کدام از session و seq که در مسیر باشند بخش بعدی مسیر را برمی‌دارند. پس seq در مسیر، هر جا که session باشد، کار می‌کند.

XHTTP: سازندهٔ padding ممکن بود هرگز تمام نشود

کامیت: ee39f4889

مشکل: رشتهٔ تصادفی padding برای مجموعه‌نویسه‌ای که اندازه‌اش ۲۵۶ را می‌شمارد هرگز تمام نمی‌شد، چون همهٔ بایت‌های تصادفی رد می‌شدند. padding از مجموعه‌ای ۶۲ نویسه‌ای استفاده می‌کند، پس هیچ کانفیگ فعلی به این مشکل نمی‌خورد.

راه‌حل: حد دیگر سرریز نمی‌کند و سازنده برای هر مجموعه‌نویسه‌ای تمام می‌شود.

Shadowsocks obfs: هدر Date سرور HTTP جعلی به نظر می‌رسید

کامیت: 4aef1b3ca

مشکل: سرور HTTP obfs هدر Date را با منطقهٔ زمانی محلی سرور می‌نوشت. nginx واقعی همیشه GMT می‌فرستد، پس پاسخ به‌راحتی از یک وب‌سرور واقعی تشخیص داده می‌شد.

راه‌حل: هدر Date با قالب استاندارد HTTP و به وقت GMT فرستاده می‌شود.

MASQUE: نشانی bind از نوع IPv6 در port mapping رد می‌شد

کامیت: 9e374aaf0

مشکل: port mapping به شکل مستند [::1]:8080:host:port همیشه رد می‌شد، چون دونقطه‌های داخل نشانی IPv6 جداکننده حساب می‌شدند. هنوز هیچ گزینه‌ای در هیدیفای از port mapping استفاده نمی‌کند، پس هیچ کانفیگی تحت تأثیر نبود.

راه‌حل: نشانی IPv6 داخل کروشه یک بخش خوانده می‌شود و درست resolve می‌شود.

MASQUE: ‏UDP به نشانی‌های IPv6 شکست می‌خورد

کامیت: db20461ff

مشکل: ‏UDP به مقصد IPv6 از طریق MASQUE از نشانی IPv4 تونل فرستاده می‌شد و شکست می‌خورد.

راه‌حل: ‏UDP به مقصد IPv6 از نشانی IPv6 تونل استفاده می‌کند.

VLESS: پوشش Vision ممکن بود از لایهٔ زیرش رد شود

کامیت: f9eca8b90

مشکل: پوشش Vision به حلقهٔ کپی می‌گفت می‌تواند مستقیم از اتصال TLS بخواند و در آن بنویسد، حتی وقتی یک لایهٔ رمزنگاری یا transport بین آن‌ها بود. این می‌توانست داده را از کنار آن لایه رد کند. اتصال Vision بالای آن همین حالا جلوی این کار را می‌گیرد، پس هیچ ترافیکی تحت تأثیر نبود.

راه‌حل: پوشش فقط وقتی این میان‌بر را اجازه می‌دهد که اتصال زیرش هم اجازه بدهد.

VLESS: قاعده‌های padding در encryption کلید خوانده می‌شدند

کامیت: dcbc72095

مشکل: قاعدهٔ paddingی مثل 100-111-1111 در رشتهٔ encryption یا decryption ‏VLESS یک base64 معتبر هم هست. برای همین به‌عنوان کلید decode می‌شد و با خطای طول نامعتبر کلید رد می‌شد، پس نود راه نمی‌افتاد.

راه‌حل: در بخش padding رشته، بخشی که فقط رقم و خط تیره دارد قاعدهٔ padding خوانده می‌شود.

SSH: نشست UDP-over-TCP در حالت بیکار بسته نمی‌شد

کامیت: a1f14b533

مشکل: با udp_over_tcp هر اتصال UDP دو بار روی نشست SSH شمرده می‌شد و یکی از شمارش‌ها هرگز آزاد نمی‌شد. نشست همیشه مشغول به نظر می‌رسید و بعد از بیکار شدن باز می‌ماند.

راه‌حل: هر اتصال UDP-over-TCP یک بار شمرده می‌شود و نشست بیکار طبق انتظار بسته می‌شود.

SSH: ورودی server_version را نادیده می‌گرفت

کامیت: 97539d44c

مشکل: ورودی SSH ‏server_version را می‌خواند ولی هرگز به کار نمی‌برد، پس همیشه نسخهٔ پیش‌فرض کتابخانهٔ Go را اعلام می‌کرد.

راه‌حل: سرور server_version را اعلام می‌کند. به نام خالی مثل OpenSSH_9.6 پیشوند لازم SSH-2.0- اضافه می‌شود.

DNSTT: آزمون resolverها اتصال‌ها را باز می‌گذاشت

کامیت: f3b9ea0b3

مشکل: هر آزمون یک resolver از نوع DoH، DoT یا TCP اتصالی باز می‌کرد و هرگز آن را نمی‌بست. resolverی که هرگز جواب نمی‌داد هم می‌توانست آزمون را برای همیشه نگه دارد.

راه‌حل: هر آزمون اتصالش را می‌بندد و بعد از ۱۰ ثانیه منصرف می‌شود.

DNSTT: شکست در اتصال به resolver آزمون را از کار می‌انداخت

کامیت: 2048c1b84

مشکل: وقتی اتصال به resolver از نوع DoH، DoT یا TCP باز نمی‌شد، آزمون resolver خطا را نادیده می‌گرفت و از کار می‌افتاد.

راه‌حل: آزمون خطای اتصال را برمی‌گرداند.

AmneziaWG: نشانی IPv6 همتا رد می‌شد

کامیت: c59c8eb37

مشکل: همتایی با نشانی IPv6 بدون کروشه، به شکل 2001:db8::1:51820، به دستگاه داده می‌شد. دستگاه آن را رد می‌کرد و endpoint نمی‌توانست به آن همتا برسد.

راه‌حل: نشانی IPv6 همتا داخل کروشه نوشته می‌شود، به شکل [2001:db8::1]:51820.

Clash API: اتصال balancer زنجیرهٔ اشتباه نشان می‌داد

کامیت: 6bf0372a9

مشکل: برای اتصالی که از balancer می‌گذشت، فهرست اتصال‌ها زنجیره‌ای با خروجی انتخاب‌شدهٔ balancer می‌ساخت ولی همان زنجیرهٔ اصلی را نشان می‌داد. خروجی انتخاب‌شده دیده نمی‌شد.

راه‌حل: فهرست اتصال‌ها زنجیره را با خروجی انتخاب‌شدهٔ balancer نشان می‌دهد.

DNS hosts: نام‌های sslip.io ‏resolve نمی‌شدند

کامیت: 68a7c5d30

مشکل: سرور DNS ‏hosts هرگز نام‌های sslip.io را resolve نمی‌کرد، چون نام در پرسش DNS با نقطه تمام می‌شود. نام IPv6 مثل 2001-db8-0-0-0-0-0-1.sslip.io هم ممکن بود نشانی IPv4 ‏0.0.0.1 خوانده شود.

راه‌حل: نقطهٔ آخر پیش از تطبیق حذف می‌شود. اول IPv6 امتحان می‌شود و هر الگو از ابتدای یک بخش نام شروع می‌شود.

SDNS: سرور tag و type نداشت

کامیت: 8affe038b

مشکل: سرور DNS ‏sdns بدون tag و type ساخته می‌شد. خروجی detour آن هم به‌عنوان وابستگی علامت نخورده بود، پس معلوم نبود پیش از آن راه بیفتد.

راه‌حل: سرور sdns ‏tag و type خودش را می‌گیرد و detour آن پیش از خودش راه می‌افتد.

ابزارهای Xray: عدد بازهٔ خیلی بزرگ بی‌صدا عوض می‌شد

کامیت: 164930d71

مشکل: فیلد بازه‌ای مثل x_padding_bytes اگر عددی بزرگ‌تر از ظرفیتش می‌گرفت، بی‌صدا به مقدار دیگری بریده می‌شد. مثلاً 9999999999 به 1410065407 تبدیل می‌شد. همین مقدار به شکل رشته رد می‌شد.

راه‌حل: عدد خارج از بازه رد می‌شود، مثل حالت رشته‌ای.

ابزارهای Xray: ‏UUID با طول نادرست ممکن بود باعث کرش شود

کامیت: b291486b7

مشکل: خواندن UUID از ۱۶ بایت خام، وقتی طول نادرست بود، به‌جای برگرداندن خطا کرش می‌کرد. هنوز چیزی در هیدیفای از این استفاده نمی‌کند، پس هیچ کانفیگی تحت تأثیر نبود.

راه‌حل: طول نادرست خطایی برمی‌گرداند که طول را می‌گوید.

ابزارهای Xray: نویسندهٔ بافردار با پر شدن بافر متوقف می‌شد

کامیت: 8e2f9b3e5

مشکل: نویسندهٔ بافردار Xray وقتی داده از فضای خالی‌اش بزرگ‌تر بود، به‌جای فرستادن بافر و نوشتن بقیه، با خطای «buffer full» متوقف می‌شد. هنوز چیزی در هیدیفای از این نویسنده استفاده نمی‌کند، پس هیچ ترافیکی تحت تأثیر نبود.

راه‌حل: بافر پر فرستاده می‌شود و نویسنده بقیهٔ داده را می‌نویسد.

آزمون URL: آزمون لغوشده ۰ میلی‌ثانیه گزارش می‌شد

کامیت: b160c990b

مشکل: وقتی آزمون URL بعد از باز شدن اتصالش لغو می‌شد، نه تأخیری برمی‌گرداند نه خطایی. پس به‌عنوان آزمونی موفق با تأخیر ۰ میلی‌ثانیه ثبت می‌شد.

راه‌حل: آزمون لغوشده به‌صورت خطا گزارش می‌شود.

WARP: شناسهٔ نامعتبر دستگاه ثبت لایسنس را از کار می‌انداخت

کامیت: 9b503de83

مشکل: افزودن license ‏WARP به دستگاه جدید، درخواست را پیش از بررسی معتبر بودنش می‌ساخت. شناسهٔ دستگاهی که نویسهٔ کنترلی داشت به‌جای برگرداندن خطا برنامه را کرش می‌داد.

راه‌حل: خطای درخواست اول بررسی و برگردانده می‌شود.

WARP: دستگاهی که با id و auth_token داده می‌شد راه نمی‌افتاد

کامیت: 0b03e9f80

مشکل: پروفایل warp با id و auth_token دستگاه موجود را از کلادفلر می‌گرفت، ولی API کلید خصوصی را برنمی‌گرداند. پس endpoint کلیدی نداشت و راه نمی‌افتاد.

راه‌حل: این مسیر حالا کلید دستگاه را از profile.private_key می‌گیرد، آن را با کلید عمومی دستگاه مقایسه می‌کند و بدون آن راه نمی‌افتد.

تونل: override_tunnel_destination اثری نداشت

کامیت: 3f3c7eece

مشکل: گزینهٔ مسیریابی override_tunnel_destination خوانده می‌شد ولی روی اتصال اعمال نمی‌شد. چیز دیگری هم مقصد تونل را تعیین نمی‌کند، پس endpoint ‏tunnel_server همهٔ اتصال‌ها را رد می‌کرد و تونل معکوس نمی‌توانست ترافیک حمل کند.

راه‌حل: این گزینه حالا مقصد تونل اتصال را تعیین می‌کند.

تونل: کاربر ناشناخته سرور را قفل می‌کرد

کامیت: 641b3fc9e

مشکل: وقتی اتصالی برای کاربری بود که endpoint ‏tunnel_server او را نمی‌شناخت، endpoint خطا برمی‌گرداند ولی قفلش را آزاد نمی‌کرد. پس همهٔ اتصال‌های بعدی از آن گیر می‌کردند. پیام خطا هم به‌جای کاربر ناشناخته، نام فرستنده را می‌آورد.

راه‌حل: قفل در این مسیر آزاد می‌شود و پیام خطا نام کاربر ناشناخته را می‌آورد.

MASQUE: ‏use_ipv6 جلوی راه‌اندازی خروجی را می‌گرفت

کامیت: 8fe3e948b

مشکل: پروفایل endpoint ‏HTTP/2 روی IPv6 ندارد. از وقتی vhttp برابر auto پیش‌فرض شد، خروجی MASQUE با use_ipv6 دنبال چنین endpointی می‌گشت، شکست می‌خورد و هرگز راه نمی‌افتاد.

راه‌حل: auto اگر اسکنر endpoint ‏HTTP/2 روی IPv6 پیدا کند از آن استفاده می‌کند و در غیر این صورت روی HTTP/3 می‌ماند.

گروه DNS: یک CNAME مسموم یا خطای سرور می‌توانست برنده شود

کامیت: 46df8123e

مشکل: group در DNS اولین پاسخی را می‌پذیرفت که پس از ignore_ranges هنوز رکوردی داشت. پاسخ مسمومی که از یک CNAME و یک نشانی مسدودشده تشکیل شده بود، CNAME خود را نگه می‌داشت و بدون هیچ نشانی برنده می‌شد. پاسخ SERVFAIL یا REFUSED هم به‌عنوان پاسخ پشتیبان نگه داشته می‌شد، پس وقتی هیچ سروری نشانی نداشت، گروه ممکن بود آن را به‌جای NXDOMAIN سرور دیگری برگرداند.

راه‌حل: پرسش A یا AAAA در پاسخ به یک نشانی نیاز دارد، پس CNAME تنها کافی نیست. SERVFAIL و REFUSED شکست حساب می‌شوند و گروه سراغ سرور بعدی می‌رود. NXDOMAIN همچنان وقتی هیچ سروری پاسخی ندارد برگردانده می‌شود.

MASQUE: بستن تونل آن را متوقف نمی‌کرد

کامیت: 130720144

مشکل: بستن خروجی WARP MASQUE (هنگام بارگذاری دوباره یا خاموش شدن) تونل آن را متوقف نمی‌کرد. اگر تونل در حال اتصال بود، بستن برای همیشه گیر می‌کرد. پس از بستن هم دستگاه بسته‌شده را در حلقه‌ای بی‌وقفه می‌خواند، لاگ را با «failed to read from TUN device» پر می‌کرد و تا توقف سرویس پردازنده مصرف می‌کرد.

راه‌حل: بستن تونل اتصال در حال انجام و هر دو حلقهٔ آن را متوقف می‌کند.

REALITY: گزینه‌های fragment و record_fragment اثری نداشتند

کامیت: 59a414925

مشکل: tls.fragment و tls.record_fragment روی گره‌های REALITY پذیرفته می‌شدند ولی هرگز اعمال نمی‌شدند، پس ClientHello همیشه یکجا فرستاده می‌شد. با کلید پساکوانتومی اندازهٔ آن حدود ۱٫۷ کیلوبایت، یعنی دو بخش TCP، است که برخی شبکه‌ها آن را دور می‌ریزند.

راه‌حل: هر دو گزینه اکنون مانند دیگر کلاینت‌های TLS روی REALITY هم اعمال می‌شوند. با Xray نسخه‌های 26.10.21 و 26.3.27 آزمایش شده است.

WARP: تغییر شبکه به آن نمی‌رسید

کامیت: ab47a78e6

مشکل: نقطهٔ پایانی warp تغییر شبکه را به نقطهٔ پایانی WireGuard درونش نمی‌رساند، پس پس از جابه‌جایی میان شبکه‌ها سوکت قدیمی را نگه می‌داشت.

راه‌حل: تغییر شبکه به نقطهٔ پایانی درونی می‌رسد و آن سوکت خود را از نو می‌سازد.

XHTTP: پدینگ حالت obfs بیرون از Referer رد می‌شد

کامیت: 17c771dd4

مشکل: سرور XHTTP پدینگ را فقط از سرآیند Referer یا رشتهٔ پرس‌وجو می‌خواند. با x_padding_obfs_mode و جایگاهی دیگر (یک سرآیند، کوکی یا سرآیند سفارشی) هر درخواست با خطای ۴۰۰ رد می‌شد و کلاینت Xray یا sing-box با این تنظیمات نمی‌توانست وصل شود.

راه‌حل: سرور پدینگ را، مانند Xray، از همان جایگاهی که پیکربندی تعیین کرده می‌خواند و بررسی می‌کند. با Xray نسخه‌های 26.10.21 و 26.3.27 آزمایش شده است.

XHTTP: رقابت داده‌ها

کامیت: 0493e3f8d

مشکل: چهار بخش از وضعیت XHTTP بدون هماهنگی میان goroutineها مشترک بود: خوانندهٔ جریان دریافت، طول بافری که پس از واگذاری خوانده می‌شد، پرچم بسته‌شدن و خوانندهٔ صف بارگذاری، و نشانی‌های اتصال. زیر بار، این‌ها می‌توانستند داده را خراب کنند یا فرایند را از کار بیندازند.

راه‌حل: هر کدام اکنون محافظت می‌شود یا به‌صورت اتمی منتقل می‌شود.

gVisor: دست‌دادن ناموفق TCP می‌توانست فرایند را از کار بیندازد

کامیت: 31947f874 (فورک gVisor: d811af3)

مشکل: در پشتهٔ شبکهٔ gVisor، بسته‌ای TCP که درست پس از شکست دست‌دادن و هنگام بسته‌شدن اتصال می‌رسید، از وضعیتی استفاده می‌کرد که پاک شده بود، و کل فرایند از کار می‌افتاد. پشتهٔ gVisor در TUN و نقطه‌های پایانی MASQUE و AmneziaWG از این پشته استفاده می‌کنند.

راه‌حل: چنین بسته‌ای نادیده گرفته می‌شود و بستن اتصال پاک‌سازی را انجام می‌دهد. gVisor اکنون در replace/gvisor فورک شده است.

TUN: اتصال‌های TCP تازه پس از خطای listener رد می‌شدند

کامیت: de0aae2c2 (فورک sing-tun: abfac68)

مشکل: پشتهٔ system در TUN هر اتصال TCP تازه را به یک listener محلی می‌سپارد. هر خطا هنگام پذیرش، حتی خطایی کوتاه‌مدت مانند تمام‌شدن توصیفگرهای فایل، حلقهٔ listener را برای همیشه متوقف می‌کرد. از آن پس همهٔ اتصال‌های TCP تازه تا راه‌اندازی دوبارهٔ VPN رد می‌شدند، در حالی که UDP کار می‌کرد.

راه‌حل: برای خطاهای کوتاه‌مدت صبر می‌شود و listener ازکارافتاده دوباره ساخته می‌شود. sing-tun اکنون در replace/sing-tun فورک شده است.

WireGuard: بازیابی پس از خراب شدن شبکه

کامیت‌ها: 386158d33 و 31c0f5732 (فورک wireguard-go: f191d67، b46c414، a7a59c8، 6f870fd، 5e21bd6، a6fcdc9)

مشکل:

  • پس از خواب دستگاه، یا وقتی DPI اتصالی را نشانه‌گذاری می‌کرد، دست‌دادن‌ها از همان درگاه مبدأ پیاپی شکست می‌خوردند و WireGuard و WARP تا تغییر شبکه بازیابی نمی‌شدند.
  • حلقهٔ دریافتی که به خطای پایدار سوکت برمی‌خورد متوقف می‌شد و چیزی آن را دوباره راه نمی‌انداخت.
  • WireGuard روی میزبان یا رابطی بدون IPv6 اصلاً راه نمی‌افتاد.
  • بسته‌های نویز (جعلی): فقط اولی فرستاده می‌شد، ابتدای هر کدام بریده می‌شد، و بالاترین مقدار هر بازهٔ پیکربندی‌شده (تعداد، اندازه و تأخیر) هرگز به کار نمی‌رفت.

راه‌حل:

  • پس از دست‌دادن‌های ناموفق، سوکت روی درگاهی تازه باز می‌شود. این کار وقتی چرخهٔ دست‌دادن تسلیم می‌شود، یا پس از ۳ تلاش بی‌پاسخ وقتی نشست از پیش مرده است، انجام می‌شود، حداکثر هر ۹۰ ثانیه یک‌بار. listen_port پیکربندی‌شده حفظ می‌شود.
  • حلقهٔ دریافتی که متوقف می‌شود، سوکت را دوباره باز می‌کند.
  • وقتی IPv6 باز نمی‌شود، IPv4 حفظ می‌شود.
  • همهٔ بسته‌های نویز کامل فرستاده می‌شوند و بازه‌ها کران بالای خود را هم دربرمی‌گیرند.

DNS محلی در لینوکس: بدون جایگزین وقتی systemd-resolved ناسازگار است

کامیت: 53aa823c9

مشکل: در لینوکس، سرور DNS از نوع local وقتی سرویس systemd-resolved متدی لازم را نداشت، به‌کلی شکست می‌خورد.

راه‌حل: به /etc/resolv.conf بازمی‌گردد و وقتی systemd-resolved دوباره کار کند، به آن برمی‌گردد.

MASQUE: بدون بازگشت از HTTP/3 وقتی UDP مسدود است

کامیت: 787f27a51

مشکل: وقتی UDP مسدود بود، تلاش HTTP/3 کل مهلت اتصال را مصرف می‌کرد و زمانی برای بازگشت به HTTP/2 نمی‌ماند. نشستی هم که روی HTTP/3 شکست می‌خورد، دوباره HTTP/3 را امتحان می‌کرد.

راه‌حل: تلاش HTTP/3 حداکثر ۵ ثانیه (یا نیمی از زمان باقی‌مانده) فرصت دارد و نشست ناموفق HTTP/3 باعث می‌شود تلاش بعدی به نسخهٔ پایین‌تر برگردد.

Tailscale: اتصال کنترل از درگاه ۸۰

کامیت: a210bd114

مشکل: اتصال کنترل Tailscale ابتدا یک ارتقای HTTP رمزنشده روی درگاه ۸۰ را امتحان می‌کرد که شبکه‌های فیلترشده آن را رهگیری یا شناسایی می‌کنند، و فقط پس از تأخیری به درگاه ۴۴۳ می‌رفت.

راه‌حل: مستقیم به درگاه ۴۴۳ می‌رود. برای رفتار قبلی، TS_FORCE_NOISE_443=false را تنظیم کنید.

گزارش‌های خرابی: بی‌حد بودند و پیکربندی را همراه داشتند

کامیت: 1017225d5

مشکل: هر خرابی گزارشی می‌افزود که هرگز پاک نمی‌شد. هر گزارش رونوشتی از پیکربندی، شامل نشانی سرورها، کلیدها و گذرواژه‌ها، داشت، در حالی که گزارش‌ها برای هم‌رسانی ساخته می‌شوند.

راه‌حل: از هر نوع، ۳۲ گزارش جدیدتر تا سقف ۶۴ مگابایت نگه داشته می‌شود. گزارش‌ها دیگر پیکربندی را ندارند و رونوشت قدیمی پاک می‌شود.

SOCKS: UDP به سرورهایی که با 0.0.0.0 پاسخ می‌دهند نمی‌رسید

کامیت: 70d6601fd

مشکل: بسیاری از سرورهای SOCKS5 به درخواست UDP با نشانی 0.0.0.0 پاسخ می‌دهند، به این معنی که «همان نشانی‌ای که با آن به من رسیدی». sing-box بستهٔ UDP را به همین نشانی، یعنی خود دستگاه، می‌فرستاد.

راه‌حل: بستهٔ UDP به نشانی سرور فرستاده می‌شود.

WireGuard: بسته‌های بزرگ‌تر از مسیر دور ریخته می‌شدند

کامیت: 1ed324a7b

مشکل: sing-box روی UDP خروجی علامت «قطعه‌قطعه نشود» می‌گذارد. بستهٔ WireGuard بزرگ‌تر از ظرفیت مسیر، مثلاً در تونلی درون تونل دیگر، دور ریخته می‌شد و انتقال‌های بزرگ متوقف می‌شدند.

راه‌حل: سیستم می‌تواند بسته‌های WireGuard را قطعه‌قطعه کند، همان‌طور که WireGuard رسمی اجازه می‌دهد. udp_fragment همچنان بر آن مقدم است.

WireGuard: detour گیرکرده تونل را قفل می‌کرد

کامیت‌ها: 304bde1fd و dd032dfd7

مشکل: اتصال WireGuard از طریق detour در حالی برقرار می‌شد که قفلی که ارسال و دریافت منتظرش هستند گرفته شده بود، بدون محدودیت زمانی. detour گیرکرده کل تونل را قفل می‌کرد. همین کد رقابت داده هم داشت.

راه‌حل: آن اتصال پس از ۱۵ ثانیه رها و دوباره امتحان می‌شود، و رقابت داده رفع شده است.

WireGuard، AmneziaWG، MASQUE: شکست کند برای میزبان‌های دردسترس‌نبودنی

کامیت: b1d063442

مشکل: اتصال TCP از این تونل‌ها به میزبانی که هرگز پاسخ نمی‌دهد، یک تا دو دقیقه پیش از شکست منتظر می‌ماند، در حالی که اتصال مستقیم پس از ۱۵ ثانیه شکست می‌خورد.

راه‌حل: پس از ۱۵ ثانیه شکست می‌خورد.

Clash API: پاک‌کردن FakeIP اثری نداشت

کامیت: 3949d2479

مشکل: پاک‌کردن FakeIP در Clash API فقط فایل کش را پاک می‌کرد. با تنظیمات پیش‌فرض نگاشت‌های FakeIP در حافظه است، پس چیزی پاک نمی‌شد.

راه‌حل: پاک‌کردن، مخزن FakeIP در حال استفاده را پاک می‌کند و تخصیص نشانی را از نو آغاز می‌کند.

ترابری HTTP: لغو از سوی کلاینت به‌عنوان خطا ثبت می‌شد

کامیت: 0ac410ae9

مشکل: در سمت سرور ترابری HTTP، کلاینتی که جریانش را لغو می‌کرد به‌عنوان شکست ثبت می‌شد.

راه‌حل: به‌عنوان اتصال بسته‌شده در نظر گرفته می‌شود.

Shadowsocks 2022: «bad request salt»

کامیت: fb981ebba

مشکل: در Shadowsocks 2022 ممکن بود پاسخ پیش از پایان نخستین ارسال خوانده شود. در این حالت اتصال با «bad request salt» شکست می‌خورد، بیشتر وقتی نخستین ارسال بزرگ بود.

راه‌حل: سرآیند درخواست هنگام برقراری اتصال، پیش از استفاده از آن، نوشته می‌شود.

فایل کش: ورودی‌ها هنگام شکست ذخیره گم می‌شدند

کامیت: 6927625a1

مشکل: وقتی فایل کش نوشته نمی‌شد (دیسک پر، فایل فقط‌خواندنی)، ورودی‌های در انتظار کش DNS و FakeIP از حافظه هم حذف می‌شدند.

راه‌حل: نگه داشته می‌شوند و در تلاش بعدی ذخیره می‌شوند.

DNS: پرس‌وجوهای تکراری پس از شکست

کامیت: 42acc7903

مشکل: پرس‌وجوهای DNS یکسان منتظر پرس‌وجوی فرستاده‌شده می‌مانند. وقتی آن پرس‌وجو شکست می‌خورد، هر پرس‌وجوی منتظر جداگانه به سرور فرستاده می‌شد.

راه‌حل: پرس‌وجوهای منتظر یک پرس‌وجوی تازهٔ مشترک دارند.

خروجی DNS: اتصال‌ها هرگز بسته نمی‌شدند

کامیت: dfb54ab49

مشکل: نشست DNS از طریق خروجی dns هرگز پایان خود را گزارش نمی‌کرد. اتصال در فهرست اتصال‌ها و آمار باقی می‌ماند و برخی ورودی‌ها تا خاموش شدن منتظرش می‌ماندند.

راه‌حل: پایان نشست گزارش می‌شود.

مسیریابی: نشست‌های UDP پس از خطای مسیریابی نشت می‌کردند

کامیت: 7ea28f847

مشکل: وقتی نشست UDP از Shadowsocks، multiplex یا UDP-over-TCP مسیریابی نمی‌شد، کدی که آن را رسیدگی می‌کرد تا خاموش شدن منتظر می‌ماند.

راه‌حل: پس از خطا بازمی‌گردد.

راه‌اندازی منتظر اتصال کند multiplex می‌ماند

کامیت: 0cb109659

مشکل: راه‌اندازی سرویس ممکن بود تا ۱۵ ثانیه منتظر اتصال multiplexی بماند که از طریق سروری کند یا ازکارافتاده باز می‌شد.

راه‌حل: این کار پس از راه‌اندازی انجام می‌شود.

XHTTP: محدودیت‌های زمانی بی‌صدا نادیده گرفته می‌شدند

کامیت‌ها: 6534ce882 و 1288cc7c6

مشکل: اتصال‌های XHTTP محدودیت زمانی را می‌پذیرفتند و نادیده می‌گرفتند، پس انتظار برای همتای خاموش هرگز تمام نمی‌شد. بستن اتصال هم خطای سمت دریافت را گم می‌کرد.

راه‌حل: اتصال‌های XHTTP اعلام می‌کنند که برای محدودیت زمانی کمک لازم دارند و مسیریاب، مانند gRPC و HTTP، آن را فراهم می‌کند. خطا برگردانده می‌شود.

رمزنگاری VLESS: گیر کردن روی سرور خاموش

کامیت: 3db8eb988

مشکل: با رمزنگاری VLESS، سروری که اتصال را می‌پذیرفت و هرگز پاسخ نمی‌داد، اتصال و هر آزمایش URL از طریق آن را برای همیشه نگه می‌داشت.

راه‌حل: دست‌دادن با پایان محدودیت زمانی‌اش متوقف می‌شود.

آزمایش URL: آزمایش‌ها پس از بارگذاری دوباره ادامه می‌یافتند

کامیت: 6360e3afb

مشکل: پس از بارگذاری دوبارهٔ پیکربندی، آزمایش‌های گروه قبلی تا ۱۵ ثانیه ادامه می‌یافتند و نتیجه را برای خروجی‌هایی می‌نوشتند که شاید دیگر وجود نداشتند.

راه‌حل: بستن گروه آزمایش‌هایش را متوقف می‌کند.

TUN: DNS کل تونل را متوقف می‌کرد

کامیت: d098fded1

مشکل: پرس‌وجوهای DNS که TUN آن‌ها را می‌گرفت روی حلقهٔ بستهٔ تونل رسیدگی می‌شدند. با سرور DNS پشت پراکسی ازکارافتاده، هر پرس‌وجو همهٔ بسته‌های تونل را ۱۰ ثانیه نگه می‌داشت.

راه‌حل: هر پرس‌وجو جداگانه رسیدگی می‌شود، حداکثر ۲۵۶ تا هم‌زمان.

libbox: شیء Connections برای استفادهٔ هم‌زمان امن نبود

کامیت: 767a337ef

مشکل: اپ هر پلتفرم رویدادهای اتصال را در رشتهٔ کلاینت فرمان اعمال می‌کند و رابط کاربری همان شیء Connections را هم‌زمان در رشتهٔ اصلی فیلتر، مرتب و فهرست می‌کند. هیچ قفلی از داده‌های مشترک آن‌ها محافظت نمی‌کرد، پس فهرست اتصال‌ها می‌توانست اپ را از کار بیندازد یا موارد خراب نشان دهد.

راه‌حل: حالا همهٔ متدهای عمومی Connections قفل می‌گیرند.

Balancer: نشست‌های چسبنده به اعضای از کار افتاده می‌رسیدند

کامیت: 299f7c824

مشکل: وقتی عضوی که برای یک نشست به خاطر سپرده شده بود از کار افتاده بود، راهبرد sticky-sessions دوباره انتخاب می‌کرد، ولی بذر انتخاب در تلاش‌های پیاپی اغلب یکسان بود. پس از آخرین تلاش هم انتخاب را بدون بررسی به کار می‌برد، و در نتیجه حدود نیمی از نشست‌های تازه به عضو از کار افتاده می‌رفتند.

راه‌حل: هر تلاش با بذری تازه انتخاب می‌کند. اگر هیچ تلاشی عضو زنده‌ای پیدا نکند، نخستین عضو زنده به کار می‌رود. عضو از کار افتاده فقط وقتی به کار می‌رود که هیچ عضوی زنده نباشد.

Balancer: اعضای آزمایش‌نشده سریع‌ترین عضو حساب می‌شدند

کامیت: 26038512b

مشکل: راهبردهای consistent-hashing و sticky-sessions تأخیر خام آزمایش را می‌خواندند. تأخیر عضوی که هرگز آزمایش نشده صفر است، پس آن عضو سریع‌ترین به نظر می‌رسید و ترافیک می‌گرفت. تأخیری هم که از حافظهٔ پنهان بازیابی شده بود، مثل نتیجه‌ای تازه پذیرفته می‌شد.

راه‌حل: این راهبردها حالا همان تأخیر تعدیل‌شده‌ای را به کار می‌برند که lowest-delay و round-robin به کار می‌برند: عضو آزمایش‌نشده از کار افتاده حساب می‌شود و تأخیر حافظهٔ پنهان جریمه می‌گیرد.

Balancer: نسبت تأخیر بزرگ سرریز می‌کرد

کامیت: 9352492aa

مشکل: بیشترین تأخیر قابل قبول برابر است با کمترین تأخیر × delay_acceptable_ratio. این مقدار در یک عدد ۱۶ بیتی نگه داشته می‌شد که از ۶۵٬۵۳۵ میلی‌ثانیه به بعد از صفر شروع می‌شود. تأخیر حافظهٔ پنهان ۲۰٬۰۰۰ میلی‌ثانیه جریمه می‌گیرد، پس با نسبت ۳، دو عضو با نتیجهٔ حافظهٔ پنهان سقفی حدود ۹٬۰۰۰ میلی‌ثانیه می‌ساختند و round-robin فقط یکی از آن دو را به کار می‌برد.

راه‌حل: این سقف در یک جا محاسبه می‌شود و کمی پایین‌تر از تأخیر پایان مهلت محدود می‌شود. عضوی که مهلتش تمام شده همچنان هرگز پذیرفته نمی‌شود.

Balancer: راهبرد round-robin عضو انتخاب‌شده را گزارش نمی‌کرد

کامیت: c4c581f38

مشکل: Balancer با راهبرد round-robin همیشه عضو فعلی را خالی گزارش می‌کرد، پس اپ و Clash API هیچ عضو انتخاب‌شده‌ای نشان نمی‌دادند.

راه‌حل: حالا عضوی را گزارش می‌کند که اتصال TCP بعدی به آن می‌رود.

Balancer: بستن آن کارگر پس‌زمینه را متوقف نمی‌کرد

کامیت: e3a6b22fa

مشکل: Close کاری انجام نمی‌داد، پس کارگری که نتایج آزمایش URL را دنبال می‌کند تا توقف کامل سرویس به کار ادامه می‌داد. پس از بارگذاری دوبارهٔ پیکربندی، Balancer قبلی همچنان خودش را به‌روز می‌کرد.

راه‌حل: Close کارگر را متوقف می‌کند.

SSH: پس از شروع، اتصال پس‌زمینه برقرار نمی‌شد

کامیت: 69fa3a66e

مشکل: خروجی SSH پس از شروع، در پس‌زمینه وصل می‌شود و پس از قطع شدن دوباره وصل می‌شود، تا پیش از رسیدن ترافیک آماده و آزمایش‌شده باشد. پس از انتقال به مراحل راه‌اندازی sing-box نسخهٔ v1.15 این مرحله دیگر اجرا نمی‌شد. خروجی تا رسیدن ترافیک آماده نمی‌شد و آزمایش URL از آن می‌گذشت.

راه‌حل: اتصال پس‌زمینه دوباره پس از شروع سرویس آغاز می‌شود.

راه‌اندازی: برخی خروجی‌ها هرگز شروع نمی‌شدند و Balancer از کار می‌افتاد

کامیت: ac35f63e4

مشکل: sing-box نسخهٔ v1.15 اجزا را مرحله‌به‌مرحله راه‌اندازی می‌کند. خروجی‌هایی که برای روش راه‌اندازی قدیمی نوشته شده‌اند (balancer، psiphon، dnstt و gooserelay) فقط هنگام خاموش شدن بسته می‌شدند و هرگز شروع نمی‌شدند. Balancer شروع‌نشده راهبردی ندارد و نخستین اتصالش کل فرایند را از کار می‌انداخت.

راه‌حل: این خروجی‌ها دوباره از مراحل راه‌اندازی می‌گذرند: آماده‌سازی، شروع، و مرحلهٔ پس از شروع سرویس.

WARP: پروفایل ناقص فرایند را از کار می‌انداخت

کامیت: 35779caa3

مشکل: نقطهٔ پایانی WARP در پس‌زمینه شروع می‌شود، پس هر خطای مهلکی در آن کل فرایند را از کار می‌اندازد. سه حالت این کار را می‌کرد:

  • درخواست پروفایل شکست می‌خورد و خروجی‌ای برای تلاش دوباره از طریق آن وجود نداشت؛
  • پروفایل (از حافظهٔ پنهان، نوشته‌شده در پیکربندی، یا گرفته‌شده از API) هیچ همتا یا پورتی نداشت؛
  • پروفایل هیچ نشانی رابطی نداشت.

راه‌حل: هر حالت حالا خطا گزارش می‌کند. نشانی و پورت همتا با بررسی و به این ترتیب انتخاب می‌شوند: server و server_port پیکربندی‌شده، پورتی که در پروفایل آمده، یا پورتِ درون نام میزبان. اگر یکی از خانواده‌های نشانی (IPv4 یا IPv6) نباشد، نادیده گرفته می‌شود.

TUIC: حالت رله UDP با املای اشتباه پذیرفته می‌شد

کامیت: 36861907a

مشکل: هر مقداری برای udp_relay_mode به جز quic، مثلاً quick یا Native، بی‌صدا به معنای native بود.

راه‌حل: مقدار خالی یا native یعنی native و quic یعنی QUIC. هر مقدار دیگری هنگام بارگذاری پیکربندی خطا می‌دهد.

XHTTP: خطاهای اتصال گم می‌شدند و بارگذاری‌های ردشده پذیرفته می‌شدند

کامیت: fee26e298

مشکل:

  • وقتی جریان دریافت نمی‌توانست وصل شود، خطا گم می‌شد. فراخواننده خواننده‌ای می‌گرفت که هرگز داده‌ای برنمی‌گرداند، و اتصال تا پایان مهلت معلق می‌ماند.
  • در HTTP/2 و HTTP/3، بارگذاری‌ای که سرور رد کرده بود (هر وضعیتی جز ۲۰۰) فرستاده‌شده حساب می‌شد.
  • پرچم بسته‌شدن بدون هماهنگی از چند goroutine خوانده و نوشته می‌شد.

راه‌حل: خطای اتصال به فراخواننده می‌رسد. در HTTP/2 و HTTP/3 وضعیتی جز ۲۰۰، مانند Xray، خطاست. پرچم بسته‌شدن اتمی است.

REALITY: مقدار short_id بلند فرایند را از کار می‌انداخت

کامیت: f6f378e81

مشکل: short_id در REALITY حداکثر ۸ بایت، یعنی ۱۶ رقم هگز، است. مقدار بلندتر از بافری که در آن رمزگشایی می‌شد بیرون می‌زد و فرایند را، هم در کلاینت و هم در سرور، از کار می‌انداخت.

راه‌حل: short_id بلندتر از ۱۶ رقم هگز خطای پیکربندی است.

VLESS: دست‌دادن ناموفق اتصال را باز می‌گذاشت

کامیت: ea3c70fa5

مشکل: وقتی دست‌دادن TLS یا دست‌دادن رمزنگاری VLESS شکست می‌خورد، اتصال TCP زیرین هرگز بسته نمی‌شد. دست‌دادن رمزنگاری ناموفق همچنین اتصال را با مقداری خالی جایگزین می‌کرد که می‌توانست کد بعدی را از کار بیندازد. این برای TCP و UDP هر دو صادق بود.

راه‌حل: با هر شکست دست‌دادن، اتصال اصلی بسته می‌شود و فقط پس از دست‌دادن موفق جایگزین می‌شود.

Trojan: بلوک TLS غیرفعال فرایند را از کار می‌انداخت

کامیت: 27c97f0d8

مشکل: خروجی Trojan با بلوک tls که در آن enabled برابر false بود، یک شماره‌گیر TLS بدون تنظیمات TLS می‌ساخت و نخستین اتصال فرایند را از کار می‌انداخت.

راه‌حل: شماره‌گیر TLS فقط وقتی ساخته می‌شود که TLS فعال باشد.


اخرین بروزرسانی: 26 فوریهٔ 2026

نظرات