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() का परिणाम, रूट, बहिष्कृत पैकेज और बंद करने का कारण लॉग करें। वे चार पंक्तियाँ पैकेट कैप्चर के बिना सहमति की समस्या को दायरे की समस्या और जीवनचक्र की समस्या से अलग करती हैं।

संबंधित लेख