ग़लत धारणा यह है कि VpnService ऐप सिस्टम का कोई स्विच पलट देता है और उसके बाद फ़ोन सुरक्षित रहता है। वास्तव में जो होता है वह अधिक संकीर्ण है: ऐप सहमति माँगता है, एक इंटरफ़ेस बनाता है, फ़ाइल डिस्क्रिप्टर के ज़रिए पैकेट पढ़ता और लिखता है, और रुकने पर इंटरफ़ेस लौटा देता है। एक समय में ऐसी केवल एक सुरंग जीवित रह सकती है, और सिस्टम बिना पूछे उसे छीन सकता है। लोग प्रोटोकॉल पर जो भ्रमित करने वाला व्यवहार डालते हैं, उसका अधिकांश हिस्सा इसी स्वामित्व मॉडल से आता है।

हर जीवनचक्र संक्रमण को किनारे का मामला नहीं, डिज़ाइन का हिस्सा मानें।

भ्रम: ऐप फ़ोन का VPN चालू करता है

VpnService.prepare() से प्रवाह शुरू होता है, जो एक Intent लौटाता है जिसे उपयोगकर्ता को स्वीकार करना होता है, और establish() के साथ आगे बढ़ता है, जो बाद में भी उन कारणों से विफल हो सकता है जिन्हें ऐप नियंत्रित नहीं करता। दूसरी सुरंग बनाने से पहला इंटरफ़ेस निष्क्रिय हो जाता है और सिस्टम सूचना बदल जाती है। उस क्रम में कहीं भी ऐप पूरे डिवाइस का स्विच नहीं रखता; वह जो डिस्क्रिप्टर उसे दिया गया है और जो पैकेट उस पर आते हैं, उनका मालिक है। यहाँ तक कि स्थायी सूचना भी सिस्टम की है, ऐप के डिज़ाइन की नहीं।

चूँकि इंटरफ़ेस छीना नहीं जाता बल्कि दिया जाता है, कोई दूसरा क्लाइंट किसी भी समय जगह ले सकता है। ऐप यह कॉलबैक से जानता है, रिटर्न वैल्यू से नहीं।

ऐप को ज़बरदस्ती रोकना सूचना पैनल में इसी कारण दिखता है: सिस्टम बताता है कि उसने क्या दिया, यह नहीं कि ऐप ने क्या दिखाना चुना।

भ्रम: सहमति एक बार दी जाती है

जब सिस्टम सुरंग किसी दूसरे क्लाइंट को सौंपता है, तो वह उस binder पर रद्दीकरण लेनदेन भेजता है जो बेस क्लास onBind() से लौटाती है। जो ऐप उस binder को अपने से बदलता है, उसे रद्दीकरण लेनदेन स्वयं आगे भेजना होता है, अन्यथा VpnService.onRevoke() कभी सक्रिय नहीं होता और सेवा ऐसे डिस्क्रिप्टर में लिखती रहती है जो अब मौजूद नहीं है। उपयोगकर्ता भी मंज़ूरी वापस ले सकता है, और सिस्टम रीस्टार्ट या हमेशा-चालू पुनःकनेक्शन के दौरान सेवा रोक सकता है।

इसे संभालना लॉग की एक पंक्ति नहीं बल्कि ठोस काम है: डिस्क्रिप्टर पढ़ना बंद करें, उसे बंद करें, कनेक्टेड फ़्लैग साफ़ करें और उपयोगकर्ता को बताएँ कि वह किस स्थिति में है। फिर तय करें कि ऐप को फिर से स्थापित करना चाहिए, फिर से पूछना चाहिए या इंतज़ार करना चाहिए, क्योंकि हमेशा-चालू कॉन्फ़िगरेशन में सिस्टम सेवा को स्वयं वापस ला सकता है।

जो ऐप रद्दीकरण को स्थिति नहीं बल्कि अपवाद मानता है, वह कनेक्टेड बताता रहेगा जबकि उसका लिखा हर पैकेट गिरा दिया जाएगा, और गोपनीयता उपकरण के लिए यह सबसे बुरा विफलता मोड है।

भ्रम: इंटरफ़ेस आपके लिए ट्रैफ़िक छानता है

जो आता है वह कच्चा IP है। पकड़ना या छानना का अर्थ है IP, TCP, UDP और IPv6 हेडर पार्स करना, प्रवाह ट्रैक करना और जवाब डिस्क्रिप्टर में वापस लिखना, जो कैप्चर ऐप को सिस्टम सुविधा नहीं बल्कि छोटा नेटवर्क स्टैक बना देता है। protect() इसलिए मौजूद है कि ऐप का अपना ट्रांसपोर्ट सॉकेट स्वयं को पकड़ने के बजाय अपनी सुरंग को बचा ले, और यह एक बूलियन लौटाता है जो सुरंग चालू होने के बाद बने हर सॉकेट पर जाँच के योग्य है।

बिल्डर की अनुमत और निषिद्ध एप्लिकेशन सूचियाँ तय करती हैं कि किसका ट्रैफ़िक इंटरफ़ेस में प्रवेश करेगा। वे इंटरफ़ेस द्वारा ले जाए जाने वाले IP पैकेटों से आगे पेलोड तक पहुँच नहीं देतीं, और कोई API किसी दूसरे क्लाइंट की सुरंग उजागर नहीं करता या सिस्टम की VPN प्रोफ़ाइल सूचीबद्ध करता है। नेटवर्क क्षमताएँ कह सकती हैं कि कोई नेटवर्क VPN ट्रांसपोर्ट है, पर यह नहीं कि सुरंग किसकी है।

इसीलिए दूसरे ऐप्स के बारे में स्थिति के प्रश्नों के विरोधाभासी उत्तर मिलते हैं: जानकारी बस आपकी प्रक्रिया को उजागर नहीं की जाती।

जीवनचक्र वास्तव में क्या माँगता है

जिन स्थितियों को आप नियंत्रित करते हैं उन्हें ट्रैक करें: तैयार, स्थापित, रद्द, रुका हुआ। पुनःकनेक्शन टूटना और फिर स्थापित होना चाहिए, डिस्क्रिप्टर और ट्रांसपोर्ट सॉकेट का एक ही स्वामी होना चाहिए, क्योंकि स्थापित करने की होड़ में दो घटक एकमात्र उपलब्ध जगह के लिए लड़ेंगे। आधुनिक Android पर बैकग्राउंड शुरुआत सीमित है, इसलिए बूट ब्रॉडकास्ट कोई गारंटी नहीं है; जहाँ API स्तर अनुमति दे वहाँ अपनी सूचना के साथ फ़ोरग्राउंड सेवा का उपयोग करें।

केवल अंतिम स्थिति नहीं, संक्रमण लॉग करें, और स्थिति मशीन को इतना छोटा रखें कि हर रास्ता परखा जा सके। जिस स्थिति तक आप परीक्षण में नहीं पहुँच सकते, वह स्थिति बिना लॉग के फ़ील्ड में मिलेगी।

डिवाइस पर जीवनचक्र परखें

एमुलेटर कनेक्टिविटी नक़ली कर सकते हैं पर सहमति और रद्दीकरण का रास्ता विश्वसनीय ढंग से नहीं, इसलिए इन्हें असली डिवाइस पर चलाएँ जहाँ जगह छीनने के लिए दूसरा VPN क्लाइंट स्थापित हो।

adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp

सुरंग शुरू करें, सूचना और इंटरफ़ेस की पुष्टि करें, फिर दूसरे क्लाइंट को जोड़ें और देखें कि रद्दीकरण कॉलबैक चलता है और स्थिति साफ़ होती है। ऐप को ज़बरदस्ती रोकें और जाँचें कि क्या कुछ अब भी जीवित डिस्क्रिप्टर की रिपोर्ट करता है।

हमेशा-चालू रीस्टार्ट के बाद क्रम दोहराएँ, क्योंकि वह रास्ता उपयोगकर्ता-आरंभित कनेक्शन से अलग है। आप हरा चिह्न नहीं बल्कि ऐसा प्रमाण खोज रहे हैं कि ऐप ने उस डिस्क्रिप्टर का उपयोग बंद कर दिया जो अब उसका नहीं था।

संबंधित लेख