কেন আপনার iOS VPN অ্যাপ টানেল চালু থাকলেও সংযোগ বিচ্ছিন্ন দেখায়
আপনার iOS অ্যাপ টানেল নিয়ন্ত্রণ করে কিন্তু তার মালিক নয়, আর ম্যানেজারের অবস্থা ক্যাশে করা থাকে। আগে প্রেফারেন্স লোড করুন, তারপর স্ট্যাটাস ও ইন্টারফেস কাউন্টার পড়ুন।
আপনার অ্যাপ বলছে সংযোগ বিচ্ছিন্ন, মেনু বারে লেখা সংযুক্ত, অথচ ট্রাফিক স্পষ্টভাবে চলছে। এই ফাঁকটি কোনো রেস কন্ডিশন নয় যা রিট্রাই দিয়ে ঠিক করা যায়: অ্যাপ ও টানেল আলাদা প্রসেস, আর আপনার কোড যে অবজেক্টটি পড়ে তা সিস্টেমে সংরক্ষিত একটি কনফিগারেশনের ক্যাশে করা দৃশ্য। Apple-এর Personal VPN সারফেস একটি কন্ট্রোল API, ডেটা পাথ নয়, এবং এর অদ্ভুত আচরণের বেশিরভাগই এই একটি তথ্য থেকেই আসে। প্রতি কলে আসলে কী ঘটে, তা এখানে।
ম্যানেজার অবজেক্ট একটি ক্যাশে করা দৃশ্য, টানেল নয়
NETunnelProviderManager সিস্টেমের মালিকানাধীন একটি প্রেফারেন্স স্টোর থেকে পূর্ণ হয়, তাই loadFromPreferences শেষ না হওয়া পর্যন্ত এর প্রপার্টিগুলোর কোনো অর্থ নেই। তার আগে connection.status পড়লে আপনি একটি ডিফল্ট মান পান, যে কারণে রিলঞ্চের পর প্রথম স্ট্যাটাস রিফ্রেশে টানেল চালু থাকা সত্ত্বেও সংযোগ বিচ্ছিন্ন দেখায়। আগে লোড করুন, তারপর একই কমপ্লিশন হ্যান্ডলারে স্ট্যাটাস ও প্রোটোকল কনফিগারেশন পড়ুন। পোলিং না করে পরে পরিবর্তনগুলো NEVPNStatusDidChange দিয়ে পর্যবেক্ষণ করুন, এবং লক্ষ্য করুন যে নোটিফিকেশনটি On Demand দ্বারা শুরু হওয়া সংযোগের জন্যও আসে, যেখানে ফেরত দেওয়ার মতো কোনো অ্যাপ কলই থাকে না। প্রোটোকল সেটিংসেও একই কথা প্রযোজ্য: সেগুলো লোড করা কনফিগারেশন থেকে পড়া হয়, অ্যাপ যা পাঠিয়েছে তা থেকে নয়।
টানেল নিজে একটি Network Extension প্রোভাইডার প্রসেসে থাকে, তাই অ্যাপ সংযোগ না হারিয়েই নিজের অবস্থা হারাতে পারে। UI যা দেখায় তা ম্যানেজার থেকে পুনর্গঠন করতে হয়, শেষ সেশন থেকে মনে রাখা যায় না।
শুরুর ক্রম: সেভ, লোড, তারপর স্টার্ট
প্রোভাইডার-ভিত্তিক সংযোগের জন্য এই ক্রমে তিনটি ধাপ দরকার। প্রোভাইডার কনফিগারেশন লিখে saveToPreferences কল করুন, ম্যানেজার লোড করুন যাতে সংরক্ষিত কনফিগারেশনই আপনার হাতে থাকে, তারপর NETunnelProviderSession.startVPNTunnel() দিয়ে শুরু করুন। সেভ বাদ দিলে স্টার্টের কাছে চালু করার কিছু থাকে না; সেভের আগে লোড করলে ম্যানেজার এখনও পুরোনো কনফিগারেশনই প্রতিফলিত করে।
startVPNTunnel যে বুলিয়ান ফেরত দেয় তা কেবল বলে যে অনুরোধটি গৃহীত হয়েছে। প্রোভাইডার-স্তরের ব্যর্থতা পরে সংযোগ বিচ্ছিন্ন অবস্থার পরিবর্তন হিসেবে আসে, যা আপনি NEVPNStatusDidChange নোটিফিকেশনের মাধ্যমে দেখেন, আর নোটিফিকেশন হ্যান্ডলারকে loadFromPreferences-এর পরে connection.status আবার পড়তে হয়, কারণ ক্যাশে করা মানটি এখনও পুরোনো হতে পারে। অনুরোধ-স্তরের ব্যর্থতা কলের থ্রোয়িং রূপ থেকে সাথে সাথে ফেরে, অথবা startVPNTunnelAndReturnError:-এর ত্রুটি আউট-প্যারামিটারের মাধ্যমে। যে কোড ফেরত মানকেই ফলাফল ধরে নেয় সে এমন সংযোগের কথা জানায় যা কখনও প্রতিষ্ঠিত হয়নি। আরেকটি নীরব ব্যর্থতা হলো এমন প্রোভাইডার বান্ডেল আইডেন্টিফায়ার যা ইনস্টল করা এক্সটেনশনের সাথে মেলে না: অনুরোধ প্রত্যাখ্যাত হয়, আর বার্তার কোনো কিছুই আইডেন্টিফায়ারের দিকে ইঙ্গিত করে না। আইডেন্টিফায়ার ভুল হলে অনুরোধ সাধারণত সাথে সাথেই ব্যর্থ হয়, প্রোভাইডার লগের কোনো লাইন আসার আগেই।
স্টার্টের পর প্রেফারেন্স পুনরায় লোড করাই স্ট্যাটাস স্ক্রিনগুলোকে সঠিক করে। সিস্টেম কনফিগারেশন বদলালে ম্যানেজার আপনাকে নতুন কনফিগারেশন ঠেলে দেয় না।
ট্রাস্ট উপাদান প্রোফাইলের মাধ্যমে আসে, কখনও অ্যাপ কোড থেকে নয়
iOS ট্রাস্ট স্টোরে রুট সার্টিফিকেট যোগ করার কোনো সমর্থিত উপায় অ্যাপের নেই, তাই Personal VPN কনফিগারেশন কেবল এমন উপাদান রেফারেন্স করতে পারে যা ডিভাইস ইতিমধ্যেই বিশ্বাস করে: ইনস্টল করা প্রোফাইল, ডিভাইস ম্যানেজমেন্ট বা অন্য কারণে সিস্টেম-বিশ্বস্ত কোনো CA দ্বারা প্রতিষ্ঠিত চেইন। যে অ্যাপ প্রথম চালুতে নিজের CA বানায় আর আশা করে সিস্টেম তা মানবে, সে প্রোভাইডারের ভেতরে ভ্যালিডেশনে ব্যর্থ হবে, যেখানে অ্যাপের UI কারণটি দেখতে পায় না।
এই সীমাকে যেমন আছে তেমনি ডিজাইন সীমা হিসেবে ধরুন এবং এনরোলমেন্ট প্রোফাইলের সাথে ট্রাস্ট অ্যাঙ্কর পাঠান। VPN প্রমাণীকরণকে ট্রাস্ট সিদ্ধান্ত থেকে আলাদা রাখুন, কারণ এরা ভিন্ন জায়গায় ব্যর্থ হয় এবং ভিন্ন মানুষ ডিবাগ করে।
কাউন্টার ইন্টারফেস থেকে আসে, VPN API থেকে নয়
ম্যানেজার স্ট্যাটাস ও কনফিগারেশন প্রকাশ করে, আর প্রতি টানেলের বাইট গোনার কিছু নয়। ব্যবহারের সংখ্যা দরকার হলে getifaddrs() কল করুন এবং সংশ্লিষ্ট ইন্টারফেসের জন্য if_data পড়ুন: Wi-Fi-এর জন্য en0, সেলুলারের জন্য pdp_ip ইন্টারফেস, আর টানেলের জন্যই একটি utun ডিভাইস। utun কাউন্টার ভেতরের ট্রাফিক বর্ণনা করে এবং প্রোটোকল ওভারহেড অন্তর্ভুক্ত করে।
এই সংখ্যাগুলো আনুমানিক, অ্যাপ-ভিত্তিক হিসাব নয়, আর ইন্টারফেস পুনরায় তৈরি হলে সেগুলো রিসেট হয়। UI-তে সেভাবেই লেবেল করুন, নইলে কেউ সেগুলো দিয়ে গ্রাহককে বিল করবে। অ্যাপ-ভিত্তিক সংখ্যা দরকার হলে সম্পূর্ণ ভিন্ন কোনো ব্যবস্থা দরকার, এই ফিল্ডগুলোর আরও ভালো পাঠ নয়।
ক্লিনআপ stopVPNTunnel-এ, টার্মিনেশনে নয়
applicationWillTerminate আপনাকে কয়েক সেকেন্ড দেয়, তারপর সিস্টেম প্রসেসটি মেরে ফেলতে পারে, যা সংযোগ ভাঙার নির্ভরযোগ্য জায়গা নয়। স্টপ পাঠান stopVPNTunnel()-এ অথবা প্রোভাইডারের স্টপ কলব্যাকে সামলান, এবং আপনি যে ম্যানেজার বা কনফিগারেশন আইডেন্টিফায়ার দিয়ে শুরু করেছিলেন তা ধরে রাখুন, যাতে স্টপটি ডিভাইসের প্রতিটি কনফিগারেশনের বদলে সেই সংযোগকেই লক্ষ্য করে।
সেই আইডেন্টিফায়ারটি যে প্রোফাইলের অন্তর্গত তার পাশে সংরক্ষণ করুন, কারণ ম্যানেজার অবজেক্ট ব্যবহারকারী কোন কনফিগারেশন বেছে নিয়েছে তা লিপিবদ্ধ করে না। আর মনে রাখুন, On Demand যে সংযোগ তোলে তার পিছনে কোনো অ্যাপ কলই নেই, তাই স্ট্যাটাস স্ক্রিনকে কিছু আঁকার আগে এখনও প্রেফারেন্স লোড করতে হয়।