অধিকাংশ অ্যান্ড্রয়েড অ্যাপ VPN শনাক্ত করতে যে পরীক্ষা ব্যবহার করে তা একটি ইন্টারফেসের নাম, এবং এটি কখনোই VPN-এর পরীক্ষা ছিল না। এটি জানায় যে ডেভেলপারের প্রত্যাশিত স্ট্রিং-এ কোনো ইন্টারফেস আছে, যার অর্থ এক ডিভাইসে OpenVPN আর অন্য ডিভাইসে সম্পূর্ণ অসম্পর্কিত কিছু। ping একই ধরনের প্রমাণ: এটি প্রমাণ করে একটি গন্তব্য উত্তর দিয়েছে, এই নয় যে একটি টানেল প্যাকেটটি বহন করেছে। প্ল্যাটফর্ম আসলে যা প্রকাশ করে তা হলো নেটওয়ার্কের একটি সেট এবং প্রতিটি নেটওয়ার্ক যে ট্রান্সপোর্ট ঘোষণা করে, আর এটি ভিন্ন উত্তরসহ ভিন্ন প্রশ্ন।

ভ্রান্ত ধারণা: tun0 থাকলেই VPN চালু আছে

অ্যান্ড্রয়েডের VPN অ্যাপগুলো VpnService-এর বাস্তবায়ন, যারা সিস্টেমকে একটি tun ফাইল ডেসক্রিপ্টর দেয়। ইন্টারফেসের নাম আসে যে এটি তৈরি করেছে তার কাছ থেকে: OpenVPN সাধারণত tun0 তৈরি করে, PPTP ও L2TP তৈরি করে ppp0, WireGuard তার ইন্টারফেসের নাম দেয় টানেলের নামে, আর IPsec টানেল প্রচলিত কোনো নামই নাও দিতে পারে। তাই /proc/net/dev পড়া বা NetworkInterface অবজেক্ট গণনা করা কোনো সত্য নয়, একটি রীতি খোঁজে।

ত্রুটির দুই দিকই জীবন্ত। একটি ডিভাইসে ব্যবহারকারীর VPN-এর সঙ্গে সম্পর্কহীন স্থানীয় টানেলের জন্য tun0 থাকতে পারে, আবার সম্পূর্ণ সুস্থ WireGuard টানেল কেবল tun0 স্ট্রিং চেনা পরীক্ষার কাছে অদৃশ্য থাকতে পারে। একই ফাংশনে মিথ্যা ধনাত্মক ও মিথ্যা ঋণাত্মক ফল থাকায় এটি রোগনির্ণয়ের উপায় হিসেবে অনুপযোগী, কারণ ‘কোনো VPN পাওয়া যায়নি’ বলা একটি সাপোর্ট টিকিট কোনও দিকেই বিশ্বাসযোগ্য নয়।

প্রবেশাধিকারও সংকুচিত হয়েছে। নতুন অ্যান্ড্রয়েড সংস্করণ সীমিত করে সাধারণ অ্যাপ /proc থেকে কী পড়তে পারে, তাই অ্যান্ড্রয়েড 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 ফিরিয়েছে, তা সরাসরি রাউটিং বা বর্জন তালিকার দিকে ইঙ্গিত করে, আর সেটি এমন সমাধান যার ওপর কেউ কাজ করতে পারে।

সম্পর্কিত নিবন্ধ