Android VpnService: TUN पढ़ाव अटकना बनाम ENOBUFS लेखन त्रुटियाँ
कभी न लौटने वाला पढ़ाव और ENOBUFS के साथ विफल होने वाला लेखन विपरीत कारण रखते हैं। सुधार चुनने के लिए घोषित विंडो और पैकेट की लंबाई मापें।
एक ही VpnService लूप में दो लक्षण दिखते हैं और दोनों पर एक ही चीज़ का दोष मढ़ा जाता है: एक पाठक जो कभी नहीं लौटता, और एक लेखन जो ENOBUFS के साथ विफल होता है। ये एक ही बग के रूपांतर नहीं हैं। अटका हुआ पढ़ाव सहकर्मी के साथ प्रवाह नियंत्रण पर असहमति है; विफल लेखन वह पैकेट है जिसे इंटरफ़ेस स्वीकार नहीं करेगा। यदि आप इन्हें एक समस्या मानेंगे, तो आप ऐसे बफ़र ट्यून करने में एक दिन बिताएँगे जो कभी शामिल ही नहीं थे। नीचे का विभाजन यह है कि मैं पहले लूप के किस आधे हिस्से को मापूँ।
एक लूप, दो स्वतंत्र विफलता बिंदु
आपकी सेवा TUN फ़ाइल डिस्क्रिप्टर से एक IP पैकेट पढ़ती है, उसकी जाँच करती है और उसी डिस्क्रिप्टर में उत्तर लिखती है। उस पथ में जो कुछ टूटता है वह उन दो कॉलों में से एक पर सामने आता है, इसलिए दोनों को टनल टूटा हुआ बताया जाता है। कॉलों के अनुबंध अलग हैं। पढ़ाव प्रतीक्षा करता है कि कर्नेल आपको पैकेट दे, और लेखन कर्नेल को ऐसा पैकेट देता है जिसे अस्वीकार करने का उसे अधिकार है।
पढ़ाव सोचने के लिए आसान आधा हिस्सा है। जो पढ़ाव कुछ नहीं लौटाता वह या तो ऐसे ट्रैफ़िक की प्रतीक्षा कर रहा है जो कभी नहीं आता या इसलिए प्रतीक्षा कर रहा है कि सहकर्मी ने भेजना बंद कर दिया।
अटका हुआ पढ़ाव प्रवाह नियंत्रण पर असहमति है
TUN डिवाइस पढ़ना तब तक अवरुद्ध होता है जब तक पैकेट उपलब्ध न हो। यह डिज़ाइन किया गया व्यवहार है, और इसीलिए read के अंदर खड़ा थ्रेड निष्क्रिय लिंक पर सामान्य दिखता है। यह हैंग तब बनता है जब आपका अपना TCP स्टैक स्थानीय ऐप से डेटा स्वीकार करता रहता है पर दूर के सहकर्मी को और भेजने के लिए कभी पर्याप्त विंडो घोषित नहीं करता। सहकर्मी आपकी घोषित विंडो भरता है, ACK और विंडो अद्यतन की प्रतीक्षा करता है, और रुक जाता है। तब आपके पाठक के पास लौटाने को कुछ नहीं होता, और ऐप को खुला और शांत कनेक्शन दिखता है।
सामान्य कारण हैं ऐसा स्टैक जो घोषित विंडो पर नज़र रखे बिना सॉकेट में लिखता है, और दूसरा जो अपना बफ़र खाली करने के बाद विंडो अद्यतन भेजना भूल जाता है। बाहर से दोनों एक जैसे दिखते हैं: बाइट बाहर जाते हैं, कुछ लौटता नहीं, कोई त्रुटि नहीं उठती। पाठक थ्रेड को पार्सिंग, DNS रिज़ॉल्यूशन या डिस्क I/O पर अवरुद्ध करना असंबंधित कारण से उसी आकार का लक्षण पैदा करता है, इसलिए प्रवाह नियंत्रण को दोष देने से पहले पुष्टि करें कि थ्रेड वास्तव में read के अंदर है।
सहकर्मी की भेजने की कतार और उसने अंतिम बार जो विंडो देखी उसे जाँचें। यदि आपकी विंडो शून्य होने के कारण सहकर्मी डेटा रोक रहा है, तो सुधार आपके ACK पथ में है, TUN पढ़ाव में नहीं।
ENOBUFS आकार की समस्या है, बफ़र की कमी नहीं
TUN इंटरफ़ेस पर ENOBUFS लौटाने वाला लेखन सामान्यतः पैकेट की लंबाई अस्वीकार करता है। इंटरफ़ेस की एक MTU होती है, उससे बड़ा पैकेट कतार में नहीं रखा जा सकता, और वही पैकेट फिर आज़माने पर हर बार उसी तरह विफल होता है। यही नियतत्व संकेत है। वास्तविक बफ़र की कमी दबाव घटने पर मिट जाती है, जबकि आकार का अस्वीकार इस बात की परवाह नहीं करता कि आप कितनी देर प्रतीक्षा करें।
TCP में ठीक इसके लिए एक तंत्र है, और यही वह कदम है जिसे लोग छोड़ देते हैं: MSS क्लैंप। जब आप उपयोगकर्ता स्थान में सेगमेंट संश्लेषित करते हैं तो पेलोड का आकार आप चुनते हैं, और उसे टनल की MTU के बजाय स्थानीय इंटरफ़ेस की MTU से लेने पर ऐसे सेगमेंट निकलते हैं जिन्हें टनल वहन नहीं कर सकती। दोनों में से छोटे तक सीमित करें। क्लैंप केवल TCP को कवर करता है, इसलिए बहुत बड़े UDP डेटाग्राम को आपके कोड को विखंडित या छोड़ना होगा।
डिबग करते समय आप जो भी पैकेट लिखें उसकी लंबाई लॉग करें। जो लंबाई विफल होती है वही उत्तर है, और पुनःप्रयासों के बीच वह नहीं बदलेगी।
तीन माप जो मामलों को अलग करते हैं
दोनों में से किसी भी विफलता के लिए आपको डिबगर की ज़रूरत नहीं। तीन अवलोकन आपको सही खंड तक पहुँचाने के लिए पर्याप्त हैं, और वे पुनर्निर्माण से कम समय लेते हैं।
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
यदि तीसरी पंक्ति मेल खाती है, तो लूप स्वयं ठीक है और समस्या वहाँ है जहाँ टनल उसे देख नहीं सकती: एक असुरक्षित ट्रांसपोर्ट सॉकेट जो आपका अपना VPN ट्रैफ़िक टनल में वापस ले जाता है, जो सामान्यतः ऐसे कनेक्शन के रूप में दिखता है जो सफल होता है और फिर कुछ नहीं पहुँचाता।
दोनों पथों जिस प्लंबिंग पर निर्भर हैं उसे ठीक करें
इंटरफ़ेस ठीक से बने बिना किसी भी लक्षण का पीछा करना सार्थक नहीं। महत्वपूर्ण builder कॉल पता, मार्ग, MTU और सहमति हैं, इसी क्रम में:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()MTU को टनल के वास्तविक मान पर सेट करें, न कि उदाहरण ने जो इस्तेमाल किया उस पर छोड़ दें। आगे आप जो भी लिखेंगे वह उस संख्या से सीमित है, और आपके द्वारा सेट की गई MTU तथा जिस MSS तक आप सीमित करते हैं उनके बीच असंगति सुधार के बाद लेखन विफलता वापस लाने का सबसे आम तरीका है। फिर उस सॉकेट की रक्षा करें जो VPN सर्वर तक ट्रैफ़िक ले जाता है। protect के बिना वे पैकेट आपकी ही टनल में वापस रूट हो जाते हैं और कनेक्शन कभी पूरा नहीं होता, जो अटके हुए पढ़ाव जैसा दिखता है पर वास्तव में रूटिंग लूप है।
सहमति को टाला नहीं जा सकता। prepare के null लौटाए बिना शुरू की गई सेवा को कोई इंटरफ़ेस नहीं मिलता, और establish फेंकने के बजाय null लौटाता है, इसलिए लूप शुरू करने से पहले डिस्क्रिप्टर जाँचें। VpnService संदर्भ वह जगह है जहाँ उदाहरण की नकल करने के बजाय builder अनुबंध की पुष्टि करें।
एक बार में एक दिशा सत्यापित करें
TCP छूने से पहले टनल से एक UDP प्रतिध्वनि भेजें और पैकेट की लंबाई देखें। फिर हैंडशेक खोलें और पुष्टि करें कि सेगमेंट आपके गणना किए गए MSS के नीचे रहते हैं। कनेक्शन के बजाय पैकेट गिनें, ताकि रुकावट अटके सॉकेट के बजाय काउंटर में अंतराल के रूप में दिखे। यदि लेखन के झोंके के बाद पढ़ाव रुक जाए, तो विंडो संदिग्ध है; यदि लेखन तुरंत विफल हों, तो लंबाई है।
फिर डिवाइस को रीबूट करें और एक बार दोहराएँ, क्योंकि जो VpnService केवल पहले नेटवर्क परिवर्तन तक काम करता है वह प्रवाह नियंत्रण नहीं, रूटिंग की समस्या है।