না, একটি VpnService অ্যাপ ফোনের VPN চালু করতে পারে না
অ্যাপটি একটি টানেল ও তার জীবনচক্রের মালিক, ফোনের VPN সুইচের নয়। সম্মতি প্রত্যাহার করা যায়, একসাথে কেবল একটি VPN চলে, আর প্যাকেট পার্স করা আপনার কাজ।
ভুল ধারণাটি হলো একটি VpnService অ্যাপ সিস্টেমের সুইচ উল্টে দেয় এবং তারপর থেকে ফোনটি সুরক্ষিত থাকে। আসলে যা ঘটে তা অনেক সংকীর্ণ: অ্যাপটি সম্মতি চায়, একটি ইন্টারফেস তৈরি করে, ফাইল ডেসক্রিপ্টরের মাধ্যমে প্যাকেট পড়ে ও লেখে, এবং থামলে ইন্টারফেসটি ফিরিয়ে দেয়। একসাথে কেবল একটি এমন টানেল জীবিত থাকতে পারে, আর সিস্টেম না জিজ্ঞেস করেই তা কেড়ে নিতে পারে। মানুষ প্রোটোকলের ওপর যে বিভ্রান্তিকর আচরণের দোষ চাপায় তার বেশিরভাগই এই মালিকানা মডেল থেকে আসে।
প্রতিটি জীবনচক্রের রূপান্তরকে প্রান্তিক ঘটনা নয়, ডিজাইনের অংশ হিসেবে দেখুন।
ভ্রান্ত ধারণা: অ্যাপটি ফোনের VPN চালু করে
প্রবাহটি শুরু হয় VpnService.prepare() দিয়ে, যা এমন একটি Intent ফেরত দেয় যা ব্যবহারকারীকে গ্রহণ করতে হয়, এবং চলতে থাকে establish() দিয়ে, যা এরপরেও ব্যর্থ হতে পারে এমন কারণে যা অ্যাপ নিয়ন্ত্রণ করে না। দ্বিতীয় টানেল স্থাপন করলে প্রথম ইন্টারফেস নিষ্ক্রিয় হয় এবং সিস্টেম নোটিফিকেশন প্রতিস্থাপিত হয়। এই ধারার কোথাও অ্যাপটি ডিভাইস-ব্যাপী কোনো সুইচের মালিক নয়; এটি যে ডেসক্রিপ্টর পেয়েছে এবং যেসব প্যাকেট তার ওপর আসে তার মালিক। এমনকি স্থায়ী নোটিফিকেশনও অ্যাপের ডিজাইনের নয়, সিস্টেমের।
যেহেতু ইন্টারফেসটি ছিনিয়ে নয়, দেওয়া হয়, তাই অন্য ক্লায়েন্ট যেকোনো সময় জায়গাটি নিতে পারে। অ্যাপটি তা জানতে পারে রিটার্ন ভ্যালু দিয়ে নয়, কলব্যাক দিয়ে।
অ্যাপ জোর করে থামানো নোটিফিকেশন শেডে দেখা যায় একই কারণে: সিস্টেম জানাচ্ছে সে কী দিয়েছে, অ্যাপ কী দেখাতে বেছে নিয়েছে তা নয়।
ভ্রান্ত ধারণা: সম্মতি একবারই দেওয়া হয়
সিস্টেম যখন টানেলটি অন্য ক্লায়েন্টের হাতে দেয়, তখন বেস ক্লাস onBind() থেকে যে binder ফেরত দেয় তার ওপর একটি প্রত্যাহার লেনদেন পাঠায়। যে অ্যাপ সেই binder-টি নিজের দিয়ে প্রতিস্থাপন করে, তাকে প্রত্যাহার লেনদেনটি নিজেই ফরওয়ার্ড করতে হবে, নইলে VpnService.onRevoke() কখনো চলে না এবং সার্ভিসটি এমন ডেসক্রিপ্টরে লেখা চালিয়ে যায় যা আর নেই। ব্যবহারকারীও অনুমোদন প্রত্যাহার করতে পারেন, আর সিস্টেম রিস্টার্ট বা অলওয়েজ-অন পুনঃসংযোগের সময় সার্ভিস থামাতে পারে।
এটি সামলানো লগের একটি লাইন নয়, স্পষ্ট কাজ: ডেসক্রিপ্টর পড়া বন্ধ করুন, তা বন্ধ করুন, সংযুক্ত ফ্ল্যাগ মুছুন এবং ব্যবহারকারীকে বলুন তিনি কোন অবস্থায় আছেন। তারপর ঠিক করুন অ্যাপটি পুনরায় স্থাপন করবে, আবার অনুরোধ করবে নাকি অপেক্ষা করবে, কারণ অলওয়েজ-অন কনফিগারেশনে সিস্টেম নিজেই সার্ভিসটি ফিরিয়ে আনতে পারে।
যে অ্যাপ প্রত্যাহারকে অবস্থা নয়, ব্যতিক্রম হিসেবে দেখে, সে সংযুক্ত বলে জানাবে অথচ তার লেখা প্রতিটি প্যাকেট ফেলে দেওয়া হবে, আর গোপনীয়তার টুলের জন্য এটি সবচেয়ে খারাপ ব্যর্থতার ধরন।
ভ্রান্ত ধারণা: ইন্টারফেস আপনার হয়ে ট্রাফিক ফিল্টার করে
যা আসে তা কাঁচা IP। ধরা বা ফিল্টার করা মানে IP, TCP, UDP ও IPv6 হেডার পার্স করা, ফ্লো ট্র্যাক করা এবং ডেসক্রিপ্টরে উত্তর ফিরিয়ে লেখা, যা একটি ক্যাপচার অ্যাপকে সিস্টেম সুবিধা নয়, ছোট নেটওয়ার্ক স্ট্যাক বানিয়ে দেয়। protect() আছে যাতে অ্যাপের নিজের ট্রান্সপোর্ট সকেট নিজেকে ধরার বদলে নিজের টানেল এড়িয়ে যায়, এবং এটি একটি বুলিয়ান ফেরত দেয় যা টানেল ওঠার পর তৈরি প্রতিটি সকেটে যাচাই করা উচিত।
বিল্ডারের অনুমোদিত ও নিষিদ্ধ অ্যাপ্লিকেশন তালিকা ঠিক করে কার ট্রাফিক ইন্টারফেসে ঢুকবে। তারা ইন্টারফেস যে IP প্যাকেট বহন করে তার বাইরে পেলোডে প্রবেশাধিকার দেয় না, এবং কোনো API অন্য ক্লায়েন্টের টানেল প্রকাশ করে না বা সিস্টেমের VPN প্রোফাইল তালিকাভুক্ত করে। নেটওয়ার্ক সক্ষমতা বলতে পারে একটি নেটওয়ার্ক VPN ট্রান্সপোর্ট, কিন্তু টানেলটি কার তা নয়।
এই কারণেই অন্য অ্যাপ সম্পর্কে অবস্থার প্রশ্নে পরস্পরবিরোধী উত্তর পাওয়া যায়: তথ্যটি কেবল আপনার প্রসেসের কাছে প্রকাশ করা হয় না।
জীবনচক্র আসলে কী দাবি করে
আপনি যে অবস্থাগুলো নিয়ন্ত্রণ করেন তা ট্র্যাক করুন: প্রস্তুত, প্রতিষ্ঠিত, প্রত্যাহৃত, থামানো। পুনঃসংযোগ হওয়া উচিত ভাঙন তারপর স্থাপন, ডেসক্রিপ্টর ও ট্রান্সপোর্ট সকেটের একক মালিক নিয়ে, কারণ স্থাপনের জন্য প্রতিযোগিতায় নামা দুটি উপাদান একমাত্র খালি জায়গার জন্য লড়বে। আধুনিক Android-এ ব্যাকগ্রাউন্ড স্টার্ট সীমিত, তাই বুট ব্রডকাস্ট কোনো নিশ্চয়তা নয়; API স্তর যেখানে অনুমতি দেয় সেখানে তার নোটিফিকেশনসহ ফোরগ্রাউন্ড সার্ভিস ব্যবহার করুন।
শুধু চূড়ান্ত অবস্থা নয়, রূপান্তর লগ করুন, এবং প্রতিটি পথ পরীক্ষা করার মতো ছোট রাখুন অবস্থা মেশিনটি। যে অবস্থায় আপনি পরীক্ষায় পৌঁছাতে পারেন না, সেটিই মাঠে লগ ছাড়া দেখতে পাবেন।
ডিভাইসে জীবনচক্র পরীক্ষা করুন
এমুলেটর সংযোগ নকল করতে পারে, কিন্তু সম্মতি ও প্রত্যাহারের পথ বিশ্বাসযোগ্যভাবে নয়, তাই এগুলো আসল ডিভাইসে চালান যেখানে জায়গাটি কেড়ে নিতে দ্বিতীয় একটি VPN ক্লায়েন্ট ইনস্টল করা আছে।
adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp
টানেল চালু করুন, নোটিফিকেশন ও ইন্টারফেস নিশ্চিত করুন, তারপর অন্য ক্লায়েন্ট সংযুক্ত করুন এবং দেখুন প্রত্যাহার কলব্যাক চলে কি না এবং অবস্থা পরিষ্কার হয় কি না। অ্যাপটি জোর করে থামান এবং দেখুন কিছু এখনো জীবিত ডেসক্রিপ্টরের কথা জানায় কি না।
অলওয়েজ-অন রিস্টার্টের পরে ধারাটি পুনরাবৃত্তি করুন, কারণ সেই পথ ব্যবহারকারীর শুরু করা সংযোগ থেকে আলাদা। আপনি খুঁজছেন সবুজ আইকন নয়, বরং প্রমাণ যে অ্যাপটি এমন ডেসক্রিপ্টর ব্যবহার করা বন্ধ করেছে যা আর তার নয়।