দুটি পথ একটি Android অ্যাপের সামনে IP ইন্টারফেস বসাতে পারে, আর দুটোই খুব আলাদা মালিকানার একই ধরনের ফাইল ডেসক্রিপ্টরে শেষ হয়। একটি চলে VpnService ও তার বিল্ডারের মধ্য দিয়ে, অন্যটি root শেল ও /dev/net/tun-এর মধ্য দিয়ে। ওই ডেসক্রিপ্টরে প্রতিটি পড়া একটি বহির্গামী প্যাকেট আর প্রতিটি লেখা একটি অন্তর্গামী প্যাকেট। পথ দুটিকে আলাদা করে এই যে নীতি কার হাতে, কে OS আপগ্রেড টিকে থাকে, আর রাউটিং টেবিলের কতটা তুমি ছুঁতে পারো।

প্ল্যাটফর্মের পথ ধরো, যদি না তুমি সেই নির্দিষ্ট জিনিসটির নাম বলতে পারো যা এটি করতে অস্বীকার করে। সেই অস্বীকৃতি সাধারণত TAP, নিজের অ্যাপ সেটের বাইরে ক্যাপচার, বা এমন রাউটিং নিয়ম যা ফ্রেমওয়ার্ক কখনোই প্রকাশ করে না।

প্ল্যাটফর্মের পথে তুমি কমান্ডের বদলে উদ্দেশ্য ঘোষণা করো। তুমি অন্তত একটি ঠিকানা, চাওয়া রুট, DNS সার্ভার আর ঐচ্ছিক অ্যাপ তালিকা দাও, তারপর Android-এর সিস্টেম সার্ভিস নিজের পরিচয়ে বিশেষাধিকারপ্রাপ্ত সেটআপ করে। ইন্টারফেসের নাম তুমি আগে থেকে কখনো জানো না, আর এটি তৈরি করা ioctl কখনো দাও না — এটাই মূল কথা।

IP ইন্টারফেস পাওয়ার দুটি উপায়

root পথটি পুরোনো রেসিপি। তুমি /dev/net/tun খোলো, IFF_TUN দিয়ে TUNSETIFF ডাকো, ip link দিয়ে ইন্টারফেস তোলো, আর হাতে হাতে ঠিকানা ও রুট যোগ করো। প্রতিটি ধাপ তোমাকেই ঠিক করতে হবে, এমনকি যেগুলো ফ্রেমওয়ার্ক নিঃশব্দে সামলে নিত। একটা রুট বাদ পড়লে ব্যবহারকারীর কাছে ঠিক মৃত সার্ভারের মতোই রিপোর্ট আসে।

ডিবাগিং সেটআপের মতোই আলাদা। root পথে হয় ইন্টারফেসটি আছে, নয়তো তোমার ioctl এমন ত্রুটি ফিরিয়েছে যা তুমি সরাসরি পড়তে পারো। প্ল্যাটফর্মের পথে establish() সফল হওয়ার পরেই কেবল ইন্টারফেসটি থাকে, তাই ব্যর্থতা আসে এক্সসেপশন বা null ডেসক্রিপ্টর হিসেবে, খোঁচানো যায় এমন ডিভাইস নোড হিসেবে নয়।

establish() আসলে কী ফেরায়

Builder.establish()-এর তিনটি ফল আছে এবং তাদের তিনটি আলাদা প্রতিক্রিয়া দরকার। ডেসক্রিপ্টর মানে ইন্টারফেসটি কনফিগার হয়ে গেছে ও ব্যবহারের জন্য তোমার, null মানে অনুরোধটি যায়নি, আর ছোড়া এক্সসেপশন মানে সিস্টেম সার্ভিস এটি প্রত্যাখ্যান করেছে। null আর সাফল্যকে একটি boolean-এ মিলিয়ে দেওয়া হলো এমন একটি অ্যাপের কাজ জানানোর সবচেয়ে সাধারণ উপায় যা কখনো প্যাকেট বহন করে না। Builder রেফারেন্স ঠিকানার শর্তটি বলে, আর সার্ভারকে দোষ দেওয়ার আগে সেটি পড়া উচিত।

দিক নির্ধারণ করে ডেসক্রিপ্টর, তোমার প্রোটোকল নয়। পড়া তোমাকে দেয় বহির্গামী প্যাকেট যা ইতিমধ্যে VPN-এর জন্য বাছা হয়েছে, আর লেখা অন্তর্গামী প্যাকেট আবার ইন্টারফেসে ঢোকায়। তোমার নিজের ট্রান্সপোর্ট ওই প্যাকেটগুলো এনক্রিপ্ট, যাচাই ও ফ্রেম করে, কারণ প্ল্যাটফর্ম প্যাকেটের সীমায় থেমে যায়।

Binder এক ধাপ, JNI আরেক অক্ষ

একটি builder অনুরোধ Binder হয়ে সিস্টেমে ঢোকে, আর তার নিচে কোথাও প্ল্যাটফর্ম ডিভাইস কনফিগার করতে নেটিভ কোড ডাকে। এগুলো একই কলে স্তূপীকৃত দুটি আলাদা ব্যবস্থা। Binder হলো তোমার অ্যাপ ও সিস্টেম সার্ভিসের মধ্যে আন্তঃপ্রক্রিয়া যোগাযোগ, আর JNI হলো প্রক্রিয়ার ভেতরে Java থেকে নেটিভে যাওয়ার সেতু। Android 9 AOSP ট্রি পড়লে স্তূপটি আক্ষরিক দেখায়, আর মনে রাখা ভালো যে অভ্যন্তরীণ ক্লাসের নাম ও ফাইল পাথ সংস্করণে সংস্করণে বদলায়।

যা বদলায় না তা হলো যে চুক্তির উপর তুমি ভরসা করতে পারো: builder, তার ফেরত দেওয়া ডেসক্রিপ্টর, আর তার চারপাশের সার্ভিস লাইফসাইকেল। কোনো অ্যাপেরই ইন্টারফেস পেতে অভ্যন্তরীণ সার্ভিস ট্রানজ্যাকশন ডাকা বা ডিভাইস নোড নতুন করে বানানো উচিত নয়। তোমার কোড AOSP ক্লাসের নামের উপর নির্ভর করলে, যে সংস্করণ তুমি পরীক্ষা করনি সেখানে ভেঙে পড়বে।

TUN বহন করে IP, TAP বহন করে Ethernet

TUN একটি IP ইন্টারফেস। TAP একটি Ethernet ইন্টারফেস, আর এই পার্থক্যই ঠিক করে ব্রিজিং আদৌ সম্ভব কি না। TUN ডিভাইসের কোনো MAC ঠিকানা নেই এবং ফরওয়ার্ড করার Ethernet হেডার নেই, তাই রুট যেভাবেই লেখা হোক, TUN ক্লায়েন্ট layer-2 ব্রিজে যোগ দিতে পারে না।

VpnService তোমাকে IP সংস্করণটি দেয়, যেটি অ্যাপ-স্তরের VPN-এর দরকার আর কাঁচা Ethernet স্ট্যাক ছাড়াই সমর্থনযোগ্য। তোমার ডিজাইনে ব্রিজিং লাগলে প্ল্যাটফর্মের পথ তোমাকে সেখানে নেবে না, আর root পথ নেবে কেবল যদি কার্নেল ও ডিভাইসের অনুমতি সহযোগিতা করে।

কোথায় root পথ এখনো জেতে

তিনটি কাজ মানুষকে প্ল্যাটফর্মের পথ থেকে সরিয়ে দেয়। layer-2 ব্রিজিংয়ের দরকার TAP। নিজের VPN পরিধির বাইরের অ্যাপ থেকে ট্রাফিক ক্যাপচার করতে দরকার এমন দৃশ্যমানতা যা ফ্রেমওয়ার্ক দেয় না। builder কখনো প্রকাশ করে না এমন পলিসি রাউটিং, যেমন একটি গন্তব্যের জন্য আলাদা টেবিল, দরকার নিজে ইনস্টল করা নিয়ম।

খরচ আসে পরে: আনলক করা bootloader বা root করা ইমেজ, SELinux নীতি যা তোমার পায়ের নিচে বদলে যায়, আর OTA-র পরে আর ঠিকভাবে কাজ না করা ডিভাইস। root করা রেসিপিও সাধারণ অ্যাপ বিতরণ দিয়ে পাঠানো যায় না, তাই বেশিরভাগ দল এটিকে পণ্য নয়, অভ্যন্তরীণ ডায়াগনস্টিক পথ হিসেবে রাখে।

তাদের মধ্যে বেছে নেওয়া

শুরু করো তোমার দরকারি ইন্টারফেস থেকে, তোমার হাতে থাকা কোড থেকে নয়। উত্তর যদি অ্যাপ-সীমিত রুটসহ TUN ডিভাইস হয়, প্ল্যাটফর্মের পথ ছোট এবং আপগ্রেড টিকে যায়। উত্তর যদি Ethernet ফ্রেম, অন্য অ্যাপের ক্যাপচার, বা builder যে রাউটিং নিয়ম প্রত্যাখ্যান করে তা জড়িত করে, তবে তুমি root এলাকায় এবং এটিকে পরিচালন ব্যয় হিসেবে বাজেট করতে হবে।

বেশিরভাগ তর্ক মেটায় এমন একটি পরীক্ষা: প্রথমে প্ল্যাটফর্মের পথ বানাও, establish()-এর ফলাফল দূরবর্তী সেশন অবস্থা থেকে আলাদা করে লিপিবদ্ধ করো, আর প্রতিটি দিকে একটি করে প্যাকেট নিশ্চিত করো। তারপরেই ঠিক করো অনুপস্থিত সামর্থ্য সমর্থিত পথ ছাড়ার যথার্থতা দেয় কি না।

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