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

भ्रम: अगर tun0 मौजूद है तो VPN चालू है

Android के VPN ऐप्स VpnService के कार्यान्वयन हैं जो सिस्टम को एक tun फ़ाइल डिस्क्रिप्टर देते हैं। इंटरफ़ेस का नाम उसी से आता है जिसने उसे बनाया: OpenVPN आम तौर पर tun0 बनाता है, PPTP और L2TP ppp0 बनाते हैं, WireGuard अपने इंटरफ़ेस का नाम टनल पर रखता है, और IPsec टनल शायद कोई पारंपरिक नाम ही न दे। इसलिए /proc/net/dev पढ़ना या NetworkInterface ऑब्जेक्ट गिनना किसी परिपाटी को खोजता है, तथ्य को नहीं।

गलती की दोनों दिशाएँ जीवित हैं। किसी डिवाइस में ऐसी स्थानीय टनल के लिए tun0 हो सकता है जिसका उपयोगकर्ता के VPN से कोई संबंध न हो, और पूरी तरह स्वस्थ WireGuard टनल ऐसी जाँच के लिए अदृश्य हो सकती है जो केवल tun0 स्ट्रिंग जानती है। एक ही फ़ंक्शन में झूठे सकारात्मक और झूठे नकारात्मक नतीजे उसे निदान के रूप में बेकार बना देते हैं, क्योंकि ‘कोई VPN नहीं मिला’ कहने वाला सहायता टिकट किसी भी दिशा में भरोसेमंद नहीं है।

पहुँच भी सिकुड़ी है। नए Android संस्करण सीमित करते हैं कि कोई सामान्य ऐप /proc से क्या पढ़ सकता है, इसलिए Android 8 पर काम करने वाली नाम-आधारित जाँच बिना किसी कोड बदलाव के बाद के डिवाइस पर खाली सूची लौटा सकती है। जब जाँच चुपचाप खुली दिशा में विफल होती है, तो ऐप निष्कर्ष निकालता है कि जिस हार्डवेयर पर टनल ठीक है वहाँ VPN नहीं है।

भ्रम: सफल ping का अर्थ है कि टनल ट्रैफ़िक ढो रही है

किसी आंतरिक होस्ट को ping तब सफल होता है जब उस होस्ट तक कोई मार्ग मौजूद हो। विभाजित टनलिंग में होस्ट Wi-Fi से पहुँच योग्य हो सकता है जबकि टनल चालू है और पूरी तरह कुछ और कर रही है। प्रति-ऐप VPN में ping करने वाला ऐप टनल से बाहर रखा जा सकता है, इसलिए ICMP प्रतिध्वनि उसके बाहर जाती है और लौट आती है, जो जाँच की धारणा के विपरीत साबित करता है।

ICMP और TCP भी अलग-अलग फ़िल्टर होते हैं। जो टनल प्रतिध्वनि अनुरोधों की अनुमति देती है पर किसी सेवा के लिए ज़रूरी TCP पोर्ट छोड़ देती है, वह ping परीक्षण पास करेगी और ऐप में विफल होगी, और यह दूसरी तरफ़ का फ़ायरवॉल नियम है, डिवाइस की विशेषता नहीं। सफल ping का एकमात्र ईमानदार अर्थ यह है कि इस गंतव्य ने इस क्षण इस प्रोटोकॉल का जवाब दिया।

प्लेटफ़ॉर्म वास्तव में क्या बताता है

API 21 से, ConnectivityManager.getAllNetworks() सिस्टम के जाने हुए हर नेटवर्क को लौटाता है, और getNetworkCapabilities(network) बताता है कि वह नेटवर्क क्या घोषित करता है। VPN नेटवर्क TRANSPORT_VPN रखता है। सभी नेटवर्कों पर लूप करना एक सवाल का जवाब देता है: क्या इस डिवाइस पर अभी कोई VPN चालू है?

दूसरा, अधिक सीमित सवाल अलग कॉल माँगता है। getActiveNetwork() वह नेटवर्क लौटाता है जिसका ऐप का ट्रैफ़िक अभी उपयोग करेगा, और उस पर वही क्षमता जाँचने से जवाब मिलता है: क्या मेरा ट्रैफ़िक किसी VPN से होकर जाएगा? दोनों असहमत हो सकते हैं, और यह असहमति जानकारीपूर्ण है। चालू पर सक्रिय न होने वाला VPN प्रति-ऐप रूटिंग या विभाजित-टनल कॉन्फ़िगरेशन की ओर इशारा करता है, गुम टनल की ओर नहीं।

Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

दोनों कॉल की रक्षा करें। सूचीबद्ध करने और पूछने के बीच गायब हुए नेटवर्क के लिए क्षमताएँ null हो सकती हैं, और हैंडओवर के दौरान सक्रिय नेटवर्क null हो सकता है। NET_CAPABILITY_NOT_VPN दूसरी दिशा में उपयोगी है, यह पुष्टि करने के लिए कि कोई नेटवर्क निश्चित रूप से टनल नहीं है, न कि केवल उसमें VPN ट्रांसपोर्ट फ़्लैग की कमी है।

NetworkInfo API के साथ वही भ्रम था

NetworkInfo के साथ ConnectivityManager.TYPE_VPN इस जाँच की दूसरी पीढ़ी है, और यह किसी कारण से अप्रचलित है। आधुनिक संस्करणों में टनल जुड़ी होने पर भी VPN के लिए लौटाया गया ऑब्जेक्ट null हो सकता है, इसलिए null को ‘कोई VPN नहीं’ मानने वाला कोड उन डिवाइसों पर चुपचाप झूठा नकारात्मक बताता है जहाँ जवाब सबसे ज़्यादा मायने रखता है।

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

पोलिंग के बजाय कॉलबैक पर प्रतिक्रिया दें

ऐसा NetworkRequest दर्ज करें जो TRANSPORT_VPN माँगे और NET_CAPABILITY_NOT_VPN हटाए, फिर उस अनुरोध के साथ registerNetworkCallback कॉल करें। तब onAvailable और onLost बताते हैं कि घोषणा कब बदलती है, और यही वह घटना है जो आपके लिए वास्तव में मायने रखती है। घटक रुकने पर कॉलबैक का पंजीकरण रद्द करें, वरना ऐप मृत स्क्रीन का संदर्भ रखता है और कॉलबैक चलता रहता है।

NetworkRequest req = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(req, callback);

इसके लिए ACCESS_NETWORK_STATE चाहिए, जो एक सामान्य अनुमति है: न root, न उपयोगकर्ता सहमति संवाद, न VPN अनुमति। टाइमर पर पोलिंग वह विकल्प है जिसकी ओर लोग लौटते हैं, और वह बैटरी जलाती है और फिर भी अंतराल से छोटा बदलाव चूक जाती है, जो ठीक वही बदलाव है जिसकी उपयोगकर्ता शिकायत करता है।

जवाब अब भी आपको क्या नहीं बता सकता

इनमें से कोई API यह नहीं बताता कि टनल स्वस्थ है या नहीं, वह किस सर्वर पर समाप्त होती है, या किसी विशेष ऐप का ट्रैफ़िक उसके अंदर है या नहीं। VPN नेटवर्क बिना उपयोगी मार्गों के भी मौजूद हो सकता है, और प्रति-ऐप टनल सवाल पूछने वाले ऐप को बाहर रख सकती है।

इसलिए दर्ज करें कि जाँच ने क्या देखा, केवल बूलियन नहीं। नेटवर्क पहचानकर्ता, घोषित ट्रांसपोर्ट और यह कि वह नेटवर्क सक्रिय था या नहीं, लॉग करें। ‘कोई VPN नहीं मिला’ कहने वाली रिपोर्ट बेकार है, जबकि यह कहने वाली रिपोर्ट कि किसी नेटवर्क ने TRANSPORT_VPN घोषित किया पर getActiveNetwork() ने Wi-Fi लौटाया, सीधे रूटिंग या बहिष्करण सूची की ओर इशारा करती है, और यह ऐसा सुधार है जिस पर कोई काम कर सकता है।

संबंधित लेख