जब दूसरा VPN पहले से सक्रिय हो तो VpnService सुरक्षा विफल हो जाती है
Android प्रति उपयोगकर्ता एक VPN इंटरफ़ेस की अनुमति देता है। prepare, establish, protect और स्वामित्व की जाँच क्रम में चलाएँ, फिर रद्द करना या प्रति-ऐप बहिष्करण चुनें।
आपका ऐप VpnService.prepare कॉल करता है, null पाता है, टनल बनाता है, और डिस्क्रिप्टर मान्य दिखता है, फिर भी सर्वर तक कुछ नहीं पहुँचता। या prepare एक Intent लौटाता है, उपयोगकर्ता उसे मंज़ूर करता है, और establish फिर भी विफल हो जाता है। दोनों नतीजे आम तौर पर एक ही बात कहते हैं: कोई दूसरा ऐप इस उपयोगकर्ता के लिए VPN इंटरफ़ेस पहले से रखता है, और आपकी protect कॉल ऐसे sockets चिह्नित कर रही हैं जिन्हें आपकी टनल उपयोग नहीं कर सकती। नीचे दी गई जाँचें क्रम में पूरी करें और पहली विफल जाँच पर रुकें, क्योंकि बाद का चरण पहले वाले को ठीक नहीं कर सकता।
protect छूने से पहले VPN स्लॉट जाँचें
Android हर उपयोगकर्ता को ठीक एक सक्रिय VPN इंटरफ़ेस देता है। यह प्लेटफ़ॉर्म का नियम है, वह अनुमति नहीं जो आपका ऐप माँग सके, इसलिए दो ऐसे क्लाइंट जो दोनों टनल बनना चाहते हैं, साथ नहीं रह सकते।
दूसरे ऐप के लिए सेट किया गया हमेशा-चालू VPN सबसे कठिन स्थिति है, क्योंकि सिस्टम उस ऐप को जुड़ा रखता है और अधिग्रहण अस्वीकार करता है। AdGuard जैसा ट्रैफ़िक फ़िल्टर, कॉर्पोरेट क्लाइंट या उपभोक्ता VPN स्लॉट रोके रख सकता है जबकि उपयोगकर्ता भूल चुका हो कि वह इंस्टॉल है। उपयोगकर्ता से कहें कि सेटिंग्स खोलकर VPN के नीचे दी गई प्रविष्टि पढ़े; वहाँ जो नाम है वही वह स्वामी है जिससे आप होड़ कर रहे हैं। जब तक वह प्रविष्टि डिस्कनेक्ट न हो या हमेशा-चालू सेटिंग हट न जाए, आपका VpnService इंटरफ़ेस नहीं बन सकता।
चरण 1: पढ़ें कि prepare क्या लौटाता है
VpnService.prepare एक ही प्रश्न का उत्तर देता है: क्या यह ऐप VPN बन सकता है? सहमति पहले से होने पर यह null लौटाता है, और जब उपयोगकर्ता को अभी मंज़ूरी देनी हो तो Intent लौटाता है, जिसे आपको startActivityForResult से शुरू करना होगा। null की वापसी को आगे बढ़ने की अनुमति मानें, इस बात का प्रमाण नहीं कि establish सफल होगा।
Intent intent = VpnService.prepare(context);
if (intent != null) {
startActivityForResult(intent, REQUEST_VPN);
}अगर Intent का रास्ता लौटता है और उपयोगकर्ता ने मना कर दिया, तो prepare अगली कोशिश में फिर Intent लौटाता रहेगा। लूप में दोबारा कोशिश करने के बजाय परिणाम कोड लॉग करें, क्योंकि अस्वीकृत सहमति उपयोगकर्ता का निर्णय है, कोई अस्थायी त्रुटि नहीं।
चरण 2: पुष्टि करें कि इंटरफ़ेस आपके ऐप का है
establish के बाद मिलने वाला ParcelFileDescriptor आपका स्वामित्व-प्रमाण है, और किसी भी socket कार्य से पहले उसे जाँचना उचित है। null या पहले से बंद डिस्क्रिप्टर का अर्थ है कि इंटरफ़ेस कभी बना ही नहीं, भले ही कोई अपवाद न आया हो। builder से मिला इंटरफ़ेस नाम सिस्टम के सक्रिय VPN के लिए बताए नाम से तुलना करें, परीक्षण डिवाइस पर dumpsys का उपयोग करके। अगर वे अलग हैं, तो आपकी बनाई टनल वह टनल नहीं है जो ट्रैफ़िक ले जा रही है।
adb shell dumpsys connectivityयही असंगति जुड़े आइकन के साथ मृत ट्रैफ़िक को समझाती है। सिस्टम दूसरे ऐप की टनल दिखाता है, जबकि आपका कोड ऐसे इंटरफ़ेस का डिस्क्रिप्टर रखता है जो उसका नहीं है।
चरण 3: connect से पहले protect कॉल करें
इंटरफ़ेस बनते ही आपका ऐप जो sockets खोलता है वे डिफ़ॉल्ट रूप से टनल में भेजे जाते हैं। बाहरी ट्रांसपोर्ट इस नियम का पालन नहीं कर सकता वरना एनकैप्सुलेटेड पैकेट इंटरफ़ेस में लौट आता, इसलिए VpnService protect एक socket को VPN बायपास करके नीचे के नेटवर्क का उपयोग करने के लिए चिह्नित करता है। कॉल boolean लौटाता है, और उसे अनदेखा करना ही वह रास्ता है जिससे आप ऐसे socket पर पहुँचते हैं जो स्वस्थ दिखता है और कुछ नहीं ले जाता।
connect या bind से पहले protect कॉल करें, कभी बाद में नहीं। सुरक्षा socket के जुड़ने या बँधने से पहले होनी चाहिए, क्योंकि जुड़ा हुआ socket अपना रास्ता पहले ही चुन चुका होता है और बाद का protect उसे भरोसेमंद ढंग से नहीं हिलाता। अगर protect false लौटाता है, तो रुकें और स्वामित्व फिर जाँचें: यह तब विफल होता है जब इंटरफ़ेस आपके ऐप का न हो, जो ठीक वही स्थिति है जो दूसरा VPN छोड़ जाता है। false की वापसी चरण 1 पर लौटने का संकेत है, उसी socket को फिर आज़माने का नहीं।
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);चरण 4: socket असुविधाजनक हो तो प्रक्रिया बाँधें
कुछ ट्रांसपोर्ट एक socket नहीं होते: कोई लाइब्रेरी कई खोल सकती है, या आप चाहें कि सहायक प्रक्रिया में हर कनेक्शन एक ही exit इस्तेमाल करे। ConnectivityManager.bindProcessToNetwork प्रक्रिया को चुने गए नेटवर्क से बाँधता है, और पुराना setProcessDefaultNetwork अप्रचलित होने से पहले यही काम करता था। नीचे के कनेक्शन को NET_CAPABILITY_NOT_VPN के साथ माँगें ताकि बंधन आपकी टनल के नीचे का नेटवर्क चुने, टनल खुद नहीं।
NetworkRequest request = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();प्रक्रिया बाँधना दूसरा VPN नहीं है। यह उस प्रक्रिया के लिए exit चुनता है, जो आपके ट्रांसपोर्ट के लिए उपयोगी है और एक साथ दो टनल चलाने के लिए बेकार।
उपयोगकर्ता का डिवाइस ठीक करें, कोड का रास्ता नहीं
ऐसा कोई API नहीं है जो दो ऐप्स को VPN स्लॉट साझा करने दे, इसलिए सुधार डिवाइस की स्थिति में है। उपयोगकर्ता को दो ठोस रास्ते दें: दूसरे ऐप का कनेक्शन काटें या उसकी हमेशा-चालू सेटिंग हटाएँ ताकि आपका prepare अधिग्रहण कर सके, या उस ऐप की प्रति-ऐप सेटिंग्स खोलकर अपने ऐप को उसकी टनल से बाहर रखें। बहिष्करण बाकी सबके लिए फ़िल्टर चालू रखता है और आपके ऐप को सामान्य कनेक्शन उपयोग करने देता है, जो अक्सर वही नतीजा होता है जो उपयोगकर्ता वास्तव में चाहता है।
दोनों रास्ते अपने सहायता दस्तावेज़ में लिखें, क्योंकि उपयोगकर्ता शायद ही जानते हैं कि कौन-सा ऐप VPN का मालिक है। उनसे कहें कि सेटिंग्स से सक्रिय VPN का नाम और अपने लॉग से prepare, establish और protect के परिणाम भेजें, और आप एक मिनट में बता सकते हैं कि कौन-सा चरण विफल हुआ। फिर वास्तविक ट्रैफ़िक से सत्यापित करें: नाम की खोज, एक TCP कनेक्शन और टनल से होकर एक अनुरोध, केवल जुड़ा आइकन नहीं। मान्य डिस्क्रिप्टर और हरा संकेतक अधिक से अधिक स्वामित्व साबित करते हैं, यह कभी नहीं कि ट्रांसपोर्ट रूटिंग नियमों से बच गया।
अगर चारों जाँचें पास हो जाएँ और ट्रैफ़िक फिर भी मरे, तो स्वामित्व अब आपकी समस्या नहीं है। रूटिंग और सर्वर की पहुँच की ओर बढ़ें, और वही क्रेडेंशियल ऐसे डिवाइस से जाँचें जिसमें कोई VPN ऐप इंस्टॉल न हो।