Android VpnService: TUN পড়ায় স্থবিরতা বনাম ENOBUFS লেখার ত্রুটি
কখনও না ফেরা একটি পড়া এবং ENOBUFS দিয়ে ব্যর্থ একটি লেখার কারণ বিপরীত। সমাধান বাছতে বিজ্ঞাপিত উইন্ডো ও প্যাকেটের দৈর্ঘ্য মাপুন।
একই VpnService লুপে দুটি উপসর্গ দেখা যায় এবং দুটিকেই একই জিনিসকে দোষ দেওয়া হয়: একটি পাঠক যা কখনও ফেরে না, এবং একটি লেখা যা ENOBUFS দিয়ে ব্যর্থ হয়। এরা একই বাগের ভিন্ন রূপ নয়। থেমে যাওয়া পড়া হলো peer-এর সঙ্গে flow control নিয়ে মতবিরোধ; ব্যর্থ লেখা হলো এমন একটি প্যাকেট যা ইন্টারফেস গ্রহণ করবে না। এদের এক সমস্যা হিসেবে দেখলে আপনি এমন বাফার টিউন করতেই একটি দিন নষ্ট করবেন যেগুলো কখনও জড়িত ছিল না। নিচের বিভাজনই আমার সিদ্ধান্তের ভিত্তি: লুপের কোন অর্ধেক আগে ইনস্ট্রুমেন্ট করব।
এক লুপ, দুটি স্বতন্ত্র ব্যর্থতার বিন্দু
আপনার সার্ভিস TUN ফাইল ডেসক্রিপ্টর থেকে একটি IP প্যাকেট পড়ে, তা পরীক্ষা করে এবং একই ডেসক্রিপ্টরে উত্তর লেখে। সেই পথে যা কিছু ভাঙে তা ওই দুটি কলে একটিতে প্রকাশ পায়, তাই দুটিকেই টানেল ভাঙা হিসেবে রিপোর্ট করা হয়। কল দুটির চুক্তি আলাদা। পড়া কার্নেল আপনার হাতে প্যাকেট দেওয়ার জন্য অপেক্ষা করে, আর লেখা কার্নেলকে এমন প্যাকেট দেয় যা প্রত্যাখ্যান করার অধিকার তার আছে।
পড়া নিয়ে ভাবা সহজ অর্ধেক। যে পড়া কিছু ফেরায় না, সে হয় কখনও না আসা ট্রাফিকের জন্য অপেক্ষা করছে, নয়তো peer পাঠানো বন্ধ করেছে বলে অপেক্ষা করছে।
থেমে যাওয়া পড়া flow control নিয়ে মতবিরোধ
TUN ডিভাইস পড়া প্যাকেট পাওয়া পর্যন্ত ব্লক করে। এটি নকশার আচরণ, এবং এজন্যই read-এর ভিতরে থেমে থাকা থ্রেড নিষ্ক্রিয় লিংকে স্বাভাবিক দেখায়। এটি হ্যাং হয়ে যায় যখন আপনার নিজের TCP স্ট্যাক লোকাল অ্যাপ থেকে ডেটা নিতে থাকে কিন্তু দূরের peer-কে আরও পাঠানোর জন্য যথেষ্ট উইন্ডো বিজ্ঞাপন করে না। peer আপনার বিজ্ঞাপিত উইন্ডো ভরে, ACK ও একটি উইন্ডো আপডেটের অপেক্ষা করে, তারপর থেমে যায়। তখন আপনার পাঠকের ফেরানোর কিছু থাকে না, আর অ্যাপ একটি খোলা ও নীরব সংযোগ দেখে।
সাধারণ কারণ হলো এমন স্ট্যাক যা বিজ্ঞাপিত উইন্ডো ট্র্যাক না করেই সকেটে লেখে, এবং এমন স্ট্যাক যা নিজের বাফার খালি করার পর উইন্ডো আপডেট পাঠাতে ভোলে। বাইরে থেকে দুটোই একই রকম দেখায়: বাইট বেরোয়, কিছু ফেরে না, কোনো ত্রুটি ওঠে না। পার্সিং, DNS রিজলিউশন বা ডিস্ক I/O-তে পাঠক থ্রেড ব্লক করলে অসংলগ্ন কারণে একই আকারের উপসর্গ তৈরি হয়, তাই flow control-কে দোষ দেওয়ার আগে থ্রেডটি সত্যিই read-এর ভিতরে আছে কি না নিশ্চিত করুন।
peer-এর send queue এবং সে শেষ যে উইন্ডো দেখেছে তা পরীক্ষা করুন। আপনার উইন্ডো শূন্য বলে peer ডেটা ধরে রাখলে সমাধান আপনার ACK পথে, TUN পড়ায় নয়।
ENOBUFS আকারের সমস্যা, বাফারের ঘাটতি নয়
TUN ইন্টারফেসে ENOBUFS ফেরানো লেখা সাধারণত প্যাকেটের দৈর্ঘ্য প্রত্যাখ্যান করে। ইন্টারফেসের একটি MTU আছে, তার চেয়ে বড় প্যাকেট কিউতে দেওয়া যায় না, এবং একই প্যাকেট আবার চেষ্টা করলে প্রতিবার একইভাবে ব্যর্থ হয়। এই নিশ্চয়তাই সূত্র। সত্যিকারের বাফার ঘাটতি চাপ কমলে কেটে যায়, কিন্তু আকারের প্রত্যাখ্যান আপনি কতক্ষণ অপেক্ষা করলেন তা গ্রাহ্য করে না।
TCP-এর ঠিক এর জন্য একটি ব্যবস্থা আছে, আর এটিই মানুষ যে ধাপটি এড়িয়ে যায়: MSS clamp। ইউজার স্পেসে সেগমেন্ট তৈরি করার সময় পেলোডের আকার আপনি বাছেন, এবং টানেলের MTU-এর বদলে লোকাল ইন্টারফেসের MTU থেকে তা নিলে এমন সেগমেন্ট বেরোয় যা টানেল বহন করতে পারে না। দুটির ছোটটিতে clamp করুন। clamp শুধু TCP-কে ঢাকে, তাই অতিরিক্ত বড় UDP ডেটাগ্রাম আপনার কোডকে ফ্র্যাগমেন্ট বা বাদ দিতে হবে।
ডিবাগ করার সময় আপনি যে প্রতিটি প্যাকেট লেখেন তার দৈর্ঘ্য লগ করুন। যে দৈর্ঘ্য ব্যর্থ হয় সেটিই উত্তর, এবং পুনঃচেষ্টার মধ্যে তা বদলাবে না।
তিনটি পরিমাপ যা কেসগুলো আলাদা করে
দুটো ব্যর্থতার কোনোটির জন্যই ডিবাগার দরকার নেই। তিনটি পর্যবেক্ষণই আপনাকে সঠিক অংশে পৌঁছে দিতে যথেষ্ট, আর এগুলো রিবিল্ডের চেয়ে কম সময় নেয়।
| What you see | Where to look |
|---|---|
| Reader parked, peer send queue empty, last advertised window zero | Window updates in your ACK path |
| Write fails on one length and succeeds on a smaller one | MSS clamp and tunnel MTU |
| Both directions look fine, app still sees no data | Protected socket and routing loop |
তৃতীয় সারি মিললে লুপ নিজেই ঠিক আছে এবং সমস্যা সেখানে যেখানে টানেল তা দেখতে পায় না: একটি অসুরক্ষিত ট্রান্সপোর্ট সকেট যা আপনার নিজের VPN ট্রাফিককে টানেলে ফিরিয়ে নেয়, যা সাধারণত এমন সংযোগ হিসেবে দেখা যায় যা সফল হয় তারপর কিছুই পৌঁছায় না।
দুই পথই যে প্লাম্বিংয়ের উপর নির্ভর করে তা ঠিক করুন
ইন্টারফেস সঠিকভাবে তৈরি না হলে কোনো উপসর্গ তাড়া করার যোগ্য নয়। গুরুত্বপূর্ণ builder কলগুলো হলো ঠিকানা, রুট, MTU এবং সম্মতি, এই ক্রমে:
Builder()
.addAddress(10.8.0.2, 32)
.addRoute(0.0.0.0, 0)
.setMtu(1400)
.establish()MTU-কে নমুনা যা ব্যবহার করেছে তাতেই রেখে না দিয়ে টানেলের প্রকৃত মানে সেট করুন। পরে আপনি যা লেখেন সবই ওই সংখ্যা দ্বারা সীমাবদ্ধ, আর আপনি যে MTU সেট করেন এবং যে MSS-এ clamp করেন তার মধ্যে অমিলই কোনো সমাধানের পর লেখার ত্রুটি ফিরিয়ে আনার সবচেয়ে সাধারণ উপায়। তারপর যে সকেট VPN সার্ভারে ট্রাফিক বহন করে তাকে সুরক্ষিত করুন। protect ছাড়া সেই প্যাকেটগুলো আপনার নিজের টানেলে ফিরে রুট হয় এবং সংযোগ কখনও সম্পূর্ণ হয় না, যা থেমে যাওয়া পড়ার মতো দেখায় কিন্তু আসলে রাউটিং লুপ।
সম্মতি এড়ানো যায় না। prepare null না ফিরিয়ে শুরু হওয়া সার্ভিস কোনো ইন্টারফেস পায় না, এবং establish ব্যতিক্রম ছোড়ার বদলে null ফেরায়, তাই লুপ শুরু করার আগে ডেসক্রিপ্টর পরীক্ষা করুন। VpnService রেফারেন্স হলো নমুনা কপি না করে builder চুক্তি নিশ্চিত করার জায়গা।
একবারে এক দিক যাচাই করুন
TCP স্পর্শ করার আগে টানেল দিয়ে একটি UDP ইকো পাঠান এবং প্যাকেটের দৈর্ঘ্য লক্ষ্য করুন। তারপর একটি হ্যান্ডশেক খুলুন এবং সেগমেন্টগুলো আপনার গণনা করা MSS-এর নিচে থাকে কি না নিশ্চিত করুন। সংযোগ নয়, প্যাকেট গুনুন, যাতে স্থবিরতা আটকে যাওয়া সকেটের বদলে কাউন্টারে ফাঁক হিসেবে দেখা যায়। লেখার ঝাঁকের পর পড়া থেমে গেলে সন্দেহভাজন উইন্ডো; লেখা সঙ্গে সঙ্গে ব্যর্থ হলে দৈর্ঘ্য।
তারপর ডিভাইস রিবুট করে একবার পুনরাবৃত্তি করুন, কারণ যে VpnService কেবল প্রথম নেটওয়ার্ক পরিবর্তন পর্যন্ত কাজ করে তা flow control নয়, রাউটিং সমস্যা।