অন্য VPN সক্রিয় থাকলে VpnService সুরক্ষা ব্যর্থ হয়
Android প্রতি ব্যবহারকারীর জন্য একটি VPN ইন্টারফেস অনুমোদন করে। prepare, establish, protect ও মালিকানা পরীক্ষা ক্রমান্বয়ে চালান, তারপর প্রত্যাহার বা অ্যাপভিত্তিক বাদ বেছে নিন।
আপনার অ্যাপ VpnService.prepare ডাকে, null ফেরত পায়, একটি টানেল তৈরি করে, আর ডেসক্রিপ্টর বৈধ দেখায়, তবুও সার্ভারে কিছু পৌঁছায় না। অথবা prepare একটি Intent ফেরত দেয়, ব্যবহারকারী তা অনুমোদন করে, তবুও establish ব্যর্থ হয়। উভয় ফলাফল সাধারণত একই অর্থ দেয়: অন্য একটি অ্যাপ ইতিমধ্যে এই ব্যবহারকারীর VPN ইন্টারফেসের মালিক, আর আপনার protect কলগুলো এমন সকেট চিহ্নিত করছে যা আপনার টানেল ব্যবহার করতে পারে না। নিচের পরীক্ষাগুলো ক্রমান্বয়ে সম্পন্ন করুন এবং প্রথম ব্যর্থটিতে থামুন, কারণ পরের ধাপ আগেরটি মেরামত করতে পারে না।
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 পান সেটিই আপনার মালিকানার প্রমাণ, এবং যেকোনো সকেট কাজের আগে এটি যাচাই করা উচিত। null বা ইতিমধ্যে বন্ধ ডেসক্রিপ্টর মানে ইন্টারফেস কখনও তৈরি হয়নি, যদিও কোনো ব্যতিক্রম দেখা যায়নি। বিল্ডার থেকে পাওয়া ইন্টারফেস নামটি সিস্টেম সক্রিয় VPN-এর জন্য যে নাম জানায় তার সাথে তুলনা করুন, একটি পরীক্ষামূলক ডিভাইসে dumpsys ব্যবহার করে। যদি সেগুলো ভিন্ন হয়, আপনি যে টানেল তৈরি করেছেন সেটিই ট্রাফিক বহনকারী টানেল নয়।
adb shell dumpsys connectivityএকই অমিল সংযুক্ত আইকনসহ মৃত ট্রাফিক ব্যাখ্যা করে। সিস্টেম অন্য অ্যাপের টানেল দেখায়, অথচ আপনার কোড এমন ইন্টারফেসের ডেসক্রিপ্টর ধরে আছে যা তার মালিকানা নয়।
ধাপ 3: connect-এর আগে protect কল করুন
ইন্টারফেস তৈরি হওয়ার পর আপনার অ্যাপ যে সকেট খোলে সেগুলো ডিফল্টভাবে টানেলে রুট হয়। বাইরের ট্রান্সপোর্ট এই নিয়ম মানতে পারে না, নইলে এনক্যাপসুলেটেড প্যাকেট ইন্টারফেসে ফিরে যেত, তাই VpnService protect একটি সকেটকে VPN বাইপাস করে অন্তর্নিহিত নেটওয়ার্ক ব্যবহার করতে চিহ্নিত করে। কলটি একটি boolean ফেরত দেয়, আর এটি উপেক্ষা করলেই এমন সকেট পাবেন যা সুস্থ দেখায় কিন্তু কিছু বহন করে না।
connect বা bind-এর আগে protect কল করুন, কখনও পরে নয়। সকেট সংযুক্ত বা বাঁধার আগেই সুরক্ষা থাকতে হবে, কারণ সংযুক্ত সকেট ইতিমধ্যে তার রুট বেছে নিয়েছে এবং পরে protect তা নির্ভরযোগ্যভাবে সরায় না। protect যদি false ফেরত দেয়, থামুন এবং মালিকানা পুনরায় পরীক্ষা করুন: ইন্টারফেস আপনার অ্যাপের না হলে এটি ব্যর্থ হয়, যা ঠিক সেই অবস্থা যা অন্য VPN রেখে যায়। false ফেরত পাওয়া ধাপ 1-এ ফেরার সংকেত, একই সকেট আবার চেষ্টা করার নয়।
DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
return;
}
socket.connect(serverAddress, serverPort);ধাপ 4: সকেট অসুবিধাজনক হলে একটি প্রসেস বাঁধুন
কিছু ট্রান্সপোর্ট একক সকেট নয়: একটি লাইব্রেরি কয়েকটি খুলতে পারে, বা আপনি চাইতে পারেন একটি সহায়ক প্রসেসের প্রতিটি সংযোগ একই এক্সিট ব্যবহার করুক। 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 নয়। এটি ওই প্রসেসের জন্য একটি এক্সিট বেছে নেয়, যা আপনার ট্রান্সপোর্টের জন্য উপযোগী এবং একসাথে দুটি টানেল চালানোর জন্য অনুপযোগী।
ব্যবহারকারীর ডিভাইস ঠিক করুন, কোড পথ নয়
এমন কোনো API নেই যা দুটি অ্যাপকে VPN স্লট ভাগ করতে দেয়, তাই সমাধানটি ডিভাইসের অবস্থায়। ব্যবহারকারীকে দুটি নির্দিষ্ট পথ দিন: অন্য অ্যাপের সংযোগ বিচ্ছিন্ন করুন বা সবসময়-চালু মুছুন যাতে আপনার prepare দখল নিতে পারে, অথবা ওই অ্যাপের অ্যাপভিত্তিক সেটিংস খুলে আপনার অ্যাপকে তার টানেল থেকে বাদ দিন। বাদ দেওয়া বাকি সবকিছুর জন্য ফিল্টার চালু রাখে এবং আপনার অ্যাপকে সাধারণ সংযোগ ব্যবহার করতে দেয়, যা প্রায়ই ব্যবহারকারীর আসলে কাঙ্ক্ষিত ফলাফল।
উভয় পথ আপনার সহায়তা নথিতে লিখুন, কারণ ব্যবহারকারীরা খুব কমই জানেন কোন অ্যাপ VPN-এর মালিক। তাদের সেটিংস থেকে সক্রিয় VPN নাম এবং আপনার লগ থেকে prepare, establish ও protect-এর ফলাফল পাঠাতে বলুন, তাহলে এক মিনিটের মধ্যে বলতে পারবেন কোন ধাপ ব্যর্থ হয়েছে। তারপর প্রকৃত ট্রাফিক দিয়ে যাচাই করুন: একটি নাম অনুসন্ধান, একটি TCP সংযোগ এবং টানেলের মধ্য দিয়ে একটি অনুরোধ, শুধু সংযুক্ত আইকন নয়। বৈধ ডেসক্রিপ্টর ও সবুজ সূচক সর্বোচ্চ মালিকানা প্রমাণ করে, ট্রান্সপোর্ট রাউটিং নিয়ম থেকে টিকে গেছে তা কখনও নয়।
যদি চারটি পরীক্ষাই পাস করে এবং ট্রাফিক এখনও মারা যায়, মালিকানা আর আপনার সমস্যা নয়। রাউটিং ও সার্ভার পৌঁছানোর দিকে এগিয়ে যান, এবং VPN অ্যাপ ইনস্টল না থাকা ডিভাইস থেকে একই শংসাপত্র পরীক্ষা করুন।