Android VPN: VpnService डिस्क्रिप्टर बनाम root tun डिवाइस
Android ऐप ख़ुद tun0 नहीं बना सकते: establish() क्या लौटाता है, Binder कहाँ रुकता है और JNI कहाँ शुरू होता है, और root से बना डिवाइस कब जीतता है।
दो रास्ते एक Android ऐप के सामने IP इंटरफ़ेस लगा सकते हैं, और दोनों एक ही तरह के फ़ाइल डिस्क्रिप्टर पर ख़त्म होते हैं जिनके मालिक बहुत अलग हैं। एक VpnService और उसके बिल्डर से जाता है, दूसरा root शेल और /dev/net/tun से। उस डिस्क्रिप्टर पर हर पढ़ाई एक बाहर जाता पैकेट है और हर लिखाई एक अंदर आता पैकेट। रास्तों को अलग करता है कि नीति किसके पास है, OS अपग्रेड कौन झेलता है, और रूटिंग टेबल का कितना हिस्सा आप छू सकते हैं।
प्लेटफ़ॉर्म का रास्ता लें जब तक आप उस ख़ास चीज़ का नाम न बता सकें जिसे वह करने से इनकार करता है। वह इनकार आम तौर पर TAP, अपने ऐप सेट के बाहर कैप्चर, या कोई रूटिंग नियम होता है जिसे फ़्रेमवर्क कभी उजागर नहीं करता।
प्लेटफ़ॉर्म के रास्ते पर आप आदेशों की जगह इरादा घोषित करते हैं। आप कम से कम एक पता, चाहिए रूट, DNS सर्वर और वैकल्पिक ऐप सूची देते हैं, फिर Android की सिस्टम सेवा अपनी पहचान के तहत विशेषाधिकार प्राप्त सेटअप करती है। आप इंटरफ़ेस का नाम पहले से कभी नहीं जानते, और उसे बनाने वाला ioctl कभी जारी नहीं करते, और यही मुद्दा है।
IP इंटरफ़ेस पाने के दो तरीके
root रास्ता पुरानी विधि है। आप /dev/net/tun खोलते हैं, IFF_TUN के साथ TUNSETIFF कॉल करते हैं, ip link से इंटरफ़ेस ऊपर उठाते हैं, और पते तथा रूट हाथ से जोड़ते हैं। हर क़दम आपको सही करना है, वे भी जो फ़्रेमवर्क चुपचाप निपटा देता। एक रूट छूट जाए तो उपयोगकर्ता को मृत सर्वर जैसी ही रिपोर्ट मिलती है।
डिबगिंग उतनी ही अलग है जितना सेटअप। root रास्ते पर इंटरफ़ेस या तो मौजूद है या आपके ioctl ने ऐसी त्रुटि लौटाई है जिसे आप सीधे पढ़ सकते हैं। प्लेटफ़ॉर्म के रास्ते पर इंटरफ़ेस establish() सफल होने के बाद ही मौजूद होता है, इसलिए विफलता डिवाइस नोड के बजाय अपवाद या null डिस्क्रिप्टर के रूप में आती है जिसे आप कुरेद सकें।
establish() वास्तव में क्या लौटाता है
Builder.establish() के तीन परिणाम हैं और उन्हें तीन अलग प्रतिक्रियाएँ चाहिए। डिस्क्रिप्टर का अर्थ है कि इंटरफ़ेस कॉन्फ़िगर है और इस्तेमाल के लिए आपका है, null का अर्थ है कि अनुरोध पार नहीं हुआ, और फेंका गया अपवाद का अर्थ है कि सिस्टम सेवा ने उसे अस्वीकार किया। null और सफलता को एक boolean में मिला देना किसी ऐप द्वारा चलती हुई सुरंग बताने का सबसे आम तरीका है जो कभी पैकेट नहीं ढोती। Builder संदर्भ पते की शर्त बताता है, और सर्वर को दोष देने से पहले उसे पढ़ना उचित है।
दिशाएँ डिस्क्रिप्टर तय करता है, आपका प्रोटोकॉल नहीं। पढ़ाई आपको बाहर जाते पैकेट देती है जो पहले से VPN के लिए चुने गए हैं, और लिखाई अंदर आते पैकेट वापस इंटरफ़ेस में डालती है। उन पैकेटों को एन्क्रिप्ट, सत्यापित और फ़्रेम करने का काम आपके अपने ट्रांसपोर्ट का है, क्योंकि प्लेटफ़ॉर्म पैकेट की सीमा पर रुक जाता है।
Binder एक क़दम है, JNI दूसरा अक्ष
बिल्डर अनुरोध Binder से होकर सिस्टम में जाता है, और उसके नीचे कहीं प्लेटफ़ॉर्म डिवाइस कॉन्फ़िगर करने के लिए नेटिव कोड बुलाता है। ये एक ही कॉल में चढ़ी दो अलग व्यवस्थाएँ हैं। Binder आपके ऐप और सिस्टम सेवा के बीच अंतर-प्रक्रिया संचार है, जबकि JNI प्रक्रिया के भीतर Java से नेटिव तक का पुल है। Android 9 AOSP ट्री पढ़ने से स्टैक शाब्दिक दिखता है, और याद रखना चाहिए कि आंतरिक क्लास नाम और फ़ाइल पथ संस्करणों के बीच बदलते हैं।
जो नहीं बदलता वह अनुबंध है जिस पर आप भरोसा कर सकते हैं: बिल्डर, वह डिस्क्रिप्टर जो वह लौटाता है, और उसके आसपास की सेवा जीवनचक्र। कोई भी ऐप इंटरफ़ेस पाने के लिए आंतरिक सेवा लेनदेन न बुलाए और न डिवाइस नोड फिर से बनाए। अगर आपका कोड AOSP क्लास नाम पर निर्भर है, तो वह उस संस्करण पर टूटेगा जिसे आपने परखा नहीं।
TUN IP ढोता है, TAP Ethernet
TUN एक IP इंटरफ़ेस है। TAP एक Ethernet इंटरफ़ेस है, और यही अंतर तय करता है कि ब्रिजिंग संभव है भी या नहीं। TUN डिवाइस के पास MAC पता नहीं है और फ़ॉरवर्ड करने के लिए Ethernet हेडर नहीं है, इसलिए TUN क्लाइंट रूट जैसे भी लिखे हों, layer-2 ब्रिज में शामिल नहीं हो सकता।
VpnService आपको IP संस्करण देता है, जो ऐप-स्तर VPN को चाहिए और जिसे वह कच्चे Ethernet स्टैक के बिना सँभाल सकता है। अगर आपके डिज़ाइन को ब्रिजिंग चाहिए, तो प्लेटफ़ॉर्म का रास्ता आपको वहाँ नहीं ले जाएगा, और root रास्ता तभी ले जाएगा जब कर्नेल और डिवाइस अनुमतियाँ साथ दें।
root रास्ता अब भी कहाँ जीतता है
तीन काम लोगों को प्लेटफ़ॉर्म के रास्ते से दूर धकेलते हैं। layer-2 ब्रिजिंग को TAP चाहिए। अपने VPN दायरे के बाहर के ऐप्स से ट्रैफ़िक कैप्चर करने के लिए ऐसी दृश्यता चाहिए जो फ़्रेमवर्क नहीं देता। बिल्डर कभी न उजागर करने वाला नीति रूटिंग, जैसे एक गंतव्य के लिए अलग टेबल, वे नियम माँगता है जो आप ख़ुद इंस्टॉल करें।
लागत बाद में आती है: अनलॉक किया bootloader या root किया इमेज, आपके नीचे सरकती SELinux नीति, और OTA के बाद ठीक व्यवहार छोड़ देने वाला डिवाइस। root किया नुस्ख़ा सामान्य ऐप वितरण से भी नहीं भेजा जा सकता, इसलिए ज़्यादातर टीमें उसे उत्पाद के बजाय आंतरिक निदान रास्ता रखती हैं।
दोनों में से चुनना
आपके पास मौजूद कोड से नहीं, आपको चाहिए इंटरफ़ेस से शुरू करें। अगर जवाब ऐप-सीमित रूट वाला TUN डिवाइस है, तो प्लेटफ़ॉर्म का रास्ता छोटा है और अपग्रेड झेलता है। अगर जवाब में Ethernet फ़्रेम, दूसरे ऐप्स का कैप्चर, या बिल्डर के अस्वीकार किए रूटिंग नियम हैं, तो आप root क्षेत्र में हैं और उसे परिचालन लागत के रूप में बजट करना चाहिए।
ज़्यादातर बहसें तय करने वाली एक जाँच: पहले प्लेटफ़ॉर्म का रास्ता बनाएँ, establish() का परिणाम दूरस्थ सत्र की स्थिति से अलग दर्ज करें, और हर दिशा में एक पैकेट की पुष्टि करें। तभी तय करें कि छूटी हुई क्षमता समर्थित रास्ता छोड़ने को जायज़ ठहराती है या नहीं।