آپ کا Android VpnService ٹنل ٹریفک کی روٹنگ کیوں بند کر دیتا ہے
VpnService کے بارے میں چار یقین جو پروڈکشن میں ٹوٹتے ہیں: ایک وقت میں ایک ٹنل، راستوں کا دائرہ، فی ایپ فہرستیں اور بند کرنے کی ترتیب، ہر ایک کے لیے ایک جانچ کے ساتھ۔
Android VpnService کے بارے میں چار مفروضے ایک بار مجھے ہفتے کا بڑا حصہ لے گئے، اور ان میں سے کوئی بھی نیٹ ورک پروٹوکول کے بارے میں نہ تھا۔ ایپ کہہ رہی تھی منسلک ہے، چابی کا نشان اسٹیٹس بار میں بیٹھا تھا، اور ٹریفک پھر بھی معمول کے راستے سے باہر جا رہی تھی۔ میں سمجھتا رہا کہ ٹنل میری ہے جب تک میں اسے بند نہ کروں، کہ میں نے جو راستے شامل کیے وہی راستے مجھے ملتے ہیں، اور یہ کہ stopSelf بلانے سے انٹرفیس بھی ساتھ نیچے آ جائے گا۔ سروس بالکل ویسے ہی برتاؤ کر رہی تھی جیسے دستاویز میں لکھا ہے۔ ٹوٹا ہوا حصہ میرا ذہنی ماڈل تھا۔
افسانہ 1: ٹنل اس وقت تک کھڑی رہتی ہے جب تک آپ اسے بند نہ کریں
Android ایک وقت میں ایک ہی VPN انٹرفیس رکھتا ہے۔ دوسرا قائم کریں اور پلیٹ فارم اسے قطار میں نہیں لگاتا، بلکہ آپ کا غیر فعال کرتا ہے اور آپ کی سروس پر onRevoke() بلاتا ہے۔ اگر آپ کا کوڈ صرف بٹن دبانے پر سب کچھ ڈھاہتا ہے، تو قبضہ پیچھے ایک ڈسکرپٹر اور ایک ریڈر تھریڈ چھوڑ جاتا ہے، اور اگلی شروعات دونوں ورثے میں لے لیتی ہے۔
VpnService حوالہ واضح کہتا ہے کہ prepare() null واپس کرتا ہے جب ایپ کے پاس پہلے سے رضامندی ہو، اور Intent جب صارف کو ابھی منظوری دینی ہو۔ وہ رضامندی صرف اسی صورت میں پہنچتی ہے جب آپ ارادہ startActivityForResult() کے ذریعے پہنچائیں۔ یہ حوالگی چھوڑ دیں اور establish() null واپس کرتا ہے، جسے اجازت کا مسئلہ نہیں بلکہ خاموش ناکامی سمجھا جاتا ہے۔
Always-on VPN ایک ایسا مالک شامل کرتا ہے جسے آپ کنٹرول نہیں کرتے۔ نظام بوٹ کے بعد یا کریش کے بعد ٹنل واپس لا سکتا ہے، اور یہ صارف کی طرف سے ہاتھ سے شروع کی گئی VPN ایپ سے آگے نکل جاتا ہے۔ onRevoke() کو اس طرح بنائیں کہ یہ آپ کے معمول کے بند ہونے جتنے ہی وسائل بند کرے، اور اسے کونے کا معاملہ نہیں بلکہ بنیادی روکنے کا راستہ سمجھیں۔ صارفین VPN ایپس بدلتے ہیں؛ آپ کے کوڈ کو یہ سہنا ہوگا۔
افسانہ 2: addRoute بالکل وہی پکڑتا ہے جو آپ نے فہرست کیا
راستے طے کرتے ہیں کہ کون سے پیکٹ آپ کے ڈسکرپٹر تک پہنچتے ہیں، اور دائرے کی غلطی وہ چڑچڑا کرنے والی حالت پیدا کرتی ہے جس میں کچھ ٹریفک ٹنل سے گزرتی ہے اور کچھ نہیں۔ addRoute("0.0.0.0", 0) پورا IPv4 لے لیتا ہے، یعنی اب ہر پیکٹ آپ کے پاس آتا ہے اور اسے آگے بھیجنا یا گرا دینا ہوتا ہے۔ ایک ہی ہوسٹ کے لیے /32 تقریباً کچھ نہیں لیتا، اور باقی سب ایسے برتاؤ کرتا ہے جیسے آپ کا VPN کبھی انسٹال ہی نہ ہوا ہو۔
خاندان الگ الگ گنے جاتے ہیں۔ صرف IPv4 والا راستہ IPv6 کو بنیادی نیٹ ورک پر چھوڑ دیتا ہے، اس لیے دوہرے اسٹیک والا ہوسٹ ٹنل کے اندر مکمل طور پر منسلک دکھنے کے باوجود باہر نام حل کر سکتا ہے اور جڑ سکتا ہے۔
DNS بھی وہی منطق اپناتا ہے۔ addDnsServer() سے شامل کیے گئے سرور ان ایپس تک جاتے ہیں جو ٹنل کا حل کنندہ استعمال کرتی ہیں، جبکہ اپنا انکرپٹڈ DNS اینڈ پوائنٹ یا ہوسٹ نام پر سیٹ Private DNS رکھنے والی ایپ انہیں بالکل نظر انداز کر دیتی ہے۔ جب نام ایک ایپ کے لیے ناکام ہوں اور دوسری کے لیے کام کریں، تو ایک بھی راستہ چھونے سے پہلے حل کنندہ کا راستہ دیکھیں۔
افسانہ 3: فی ایپ قواعد ایک سوئچ ہیں جسے آپ دونوں طرف پلٹ سکتے ہیں
addDisallowedApplication() اور addAllowedApplication() دو فلٹر نہیں بلکہ دو موڈ ہیں۔ بلڈر فی ٹنل ایک ہی ماڈل قبول کرتا ہے، اس لیے جو ایپ تین پیکیجوں کے سوا سب کچھ چاہتی ہے وہ ممنوع فہرست استعمال کرتی ہے اور اجازت یافتہ فہرست خالی چھوڑ دیتی ہے۔ دونوں ایک دوسرے کو خارج کرتے ہیں، اور لوگوں کو یہ رن ٹائم میں تب پتہ چلتا ہے جب سیٹنگز اسکرین پہلے ہی دونوں ٹوگل پیش کر چکی ہوتی ہے۔
پیکیج کی نظر بھی کاٹتی ہے۔ Android 11 اور اس کے بعد آپ صرف ان ہی پیکیجوں کے نام لے سکتے ہیں جو آپ دیکھتے ہیں، اس لیے اس کوئری سے بنی استثنا فہرست جسے چلانے کی اجازت آپ کی ایپ کو نہیں، خاموشی سے اندراجات چھوڑ دے گی۔ فہرست پر بھروسا کرنے سے پہلے اس کا موازنہ کریں کہ پیکیج مینیجر واقعی کیا واپس کرتا ہے۔
کام کے پروفائل سامعین کو پھر تقسیم کرتے ہیں۔ پروفائل کے اندر شروع کی گئی ٹنل اس پروفائل اور اس کی ایپس کو ڈھکتی ہے، جبکہ ذاتی طرف اپنا رابطہ رکھتی ہے۔ یہ ایک بار لیے جانے والے دائرے کا فیصلہ ہے، اور اس کی جگہ سپورٹ ٹکٹ میں نہیں بلکہ انٹرفیس میں ہے۔
افسانہ 4: stopSelf ٹنل ختم کرتا ہے
نہیں۔ انٹرفیس اس ڈسکرپٹر میں رہتا ہے جو establish() واپس کرتا ہے، اور سروس آبجیکٹ مر سکتا ہے جبکہ وہ ڈسکرپٹر کھلا رہتا ہے۔ پہلے ParcelFileDescriptor بند کریں، حوالہ چھوڑ دیں، ریڈر اور رائٹر تھریڈز کو شامل کریں یا روکیں، پھر stopForeground(true) اور stopSelf() کو اسی ترتیب سے بلائیں۔
بھولا ہوا ڈسکرپٹر ہی عام وجہ ہے کہ سروس اپنی ہی بند کرنے کی درخواست سے بچی ہوئی دکھائی دیتی ہے، اور یہ اس پرانی راہ کی وضاحت کرتا ہے جو اسے شامل کرنے والی ایپ سے زیادہ زندہ رہتی ہے۔
الٹی غلطی بھی اتنی ہی عام ہے: ڈسکرپٹر بند کرنا جبکہ ریڈر تھریڈ پڑھنے کے بیچ میں ہو، اور پھر اس تھریڈ کو ایسی استثنا سنبھالنے دینا جس کے لیے کسی نے کوڈ نہیں لکھا۔ شٹ ڈاؤن ترتیب ایک ہی جگہ رکھیں اور صارف کا عمل اور onRevoke() دونوں اس میں بلائیں۔
جب ٹنل مردہ دکھائی دے تو کیا جانچیں
ٹنل اوپر آنے سے پہلے طویل عمر والے ساکٹ protect() سے محفوظ کریں۔ اس کے بغیر ٹنل کی اپنی ترسیل دوبارہ ٹنل کے اندر روٹ ہو جاتی ہے اور رابطہ اس طرح اٹک جاتا ہے جو بالکل سرور کی خرابی جیسا لگتا ہے۔ صرف ایسے پتے کے خلاف جانچ کریں جو صرف دور دراز نیٹ ورک کے پیچھے موجود ہو، کسی عوامی سائٹ کے خلاف نہیں جو کیش یا بنیادی راستے سے جواب دیتی ہو۔
establish() کا نتیجہ، راستے، مستثنیٰ پیکیج اور بند کرنے کی وجہ لاگ کریں۔ وہ چار سطریں پیکٹ کیپچر کے بغیر رضامندی کے مسئلے کو دائرے کے مسئلے اور زندگی کے چکر کے مسئلے سے الگ کرتی ہیں۔