পুরো মেশিন ছাড়াই একটি অ্যাপকে VPN দিয়ে কীভাবে পাঠাবেন
কোনো অপরিচালিত ডেস্কটপ প্রক্রিয়াভিত্তিক VPN সুইচ দেয় না। ট্রাফিক বেছে নেয় এমন স্তরটি বাছুন: বাঁধা সকেট, UID নিয়ম, ফায়ারওয়াল প্রোফাইল বা SOCKS হপ।
VPN ক্লায়েন্ট সংযোগ করে, ব্রাউজার কাজ করে, আর আপনার সত্যিই দরকারি সেই একমাত্র কমান্ডটি এখনও আপনার বাড়ির সংযোগ দিয়েই বেরোয়। অথবা উল্টোটা ঘটে: টানেল ডিফল্ট রুট দখল করে এবং স্থানীয় ফাইল সার্ভারটি ডেস্কটপ থেকে উধাও হয়ে যায়। আমি এটি দেখেছি একটি বিল্ড এজেন্টে, যার অভ্যন্তরীণ আর্টিফ্যাক্ট মিররে পৌঁছানো দরকার ছিল, যখন হোস্টের বাকি অংশ অফিস LAN-এ ছিল, আর ক্লায়েন্ট ঠিক দুটি বিকল্প দিয়েছিল: সব ট্রাফিক, নয়তো কিছুই না। দুই দিকের অনুরোধটিই একই, এই প্রোগ্রাম এবং পুরো মেশিন নয়, আর বেশিরভাগ ক্লায়েন্টে এর জন্য কোনো চেকবক্স নেই, কারণ রুট টেবিল কখনোই জানেনি একটি প্রোগ্রাম কী।
কেন রুট টেবিল আপনার প্রোগ্রাম দেখতে পায় না
প্রক্রিয়া যখন প্যাকেট পাঠায়, স্ট্যাক রাউটিং টেবিলে গন্তব্য ঠিকানাটি খোঁজে। না এক্সিকিউটেবলের নাম, না পোর্ট সেই খোঁজে ঢোকে। ডোমেন নামও একই কারণে ব্যর্থ হয়: টানেল ট্রাফিককে একটি ঠিকানায় নিয়ে যায়, আর নামটি ঠিকানায় পরিণত হওয়ার সময় পর্যন্ত রাউটিংই কার্নেলের হাতে থাকা একমাত্র লিভার। তাই „শুধু এই সাইটের জন্য VPN“ শেষে প্রক্সি বোঝায়, আর „শুধু এই প্রোগ্রামের জন্য VPN“ বোঝায় অল্প কয়েকটি পদ্ধতির একটি।
| Mechanism | What selects | Covers | On tunnel drop |
|---|---|---|---|
| Socket bind | source address | a client you control | connections fail |
| UID rule (Linux) | owning user | every process of one account | connections fail |
| Firewall profile (Windows) | adapter profile | one program | fails closed |
| Network namespace | namespace membership | everything started inside | connections fail |
| SOCKS hop | app proxy setting | one app or one browser profile | connections fail |
এর কোনোটিই প্রক্রিয়াভিত্তিক রুট নয়। প্রত্যেকটি সিদ্ধান্তটি এমন স্তরে সরায় যা প্রোগ্রাম, ব্যবহারকারী বা অ্যাপের নিজস্ব কনফিগারেশন দেখতে পায়।
একটি একক গন্তব্য এখনও একটি রুট, প্রোগ্রামের নিয়ম নয়। macOS-এ স্বাভাবিক পদ্ধতি হলো „VPN দিয়ে সব ট্রাফিক পাঠান“ থেকে টিক সরিয়ে একটি হোস্ট রুট যোগ করা: sudo route add -host internal-tuner.example -interface ppp0, যা আবার sudo route delete -host internal-tuner.example -interface ppp0 দিয়ে সরানো হয়। প্রথমে ifconfig থেকে ইন্টারফেসের নাম পড়ুন, কারণ ppp0 কেবল একটি উদাহরণ। হোস্ট রুটে ওয়াইল্ডকার্ড কাজ করে না, তাই একাধিক নামের সঙ্গে কথা বলা কোনো সার্ভিসের প্রতিটি নামের জন্য একটি করে এন্ট্রি দরকার, আর নিয়মটি মেশিনের প্রতিটি প্রক্রিয়াকে ঢাকে, আপনার মনে থাকা প্রক্রিয়াটিকে নয়।
সকেটটি টানেলের ঠিকানায় বাঁধুন
ক্লায়েন্ট আপনার নিয়ন্ত্রণে থাকলে বাঁধাই হলো সবচেয়ে সস্তা নির্বাচক। টানেলের স্থানীয় ঠিকানায় বাঁধা সকেট ওই ইন্টারফেস দিয়েই বেরোয়, যতক্ষণ সেখানে গন্তব্যের একটি রুট থাকে, আর মেশিনের বাকি সবকিছু নিজের স্বাভাবিক পথ ধরে রাখে। সূক্ষ্মতা হলো, বাঁধাই উৎস ঠিকানা বেছে নেয়, অথচ রুট টেবিলই এখনও বেরোনোর পথ বেছে নেয়। শুধু বাঁধাই টানেলে রুটহীন ট্রাফিক সরায় না, আর „আমি বাঁধলাম, তবুও ভুল দিকে যাচ্ছে“ অভিযোগগুলোর পেছনের ভুলটি এটাই।
import socket
s = socket.socket()
s.bind(("10.8.0.2", 0))
s.connect(("build.internal.example", 443))উচ্চস্তরের টুলগুলোও একই ধারণা দেয়: curl --interface 10.8.0.2, PHP-তে CURLOPT_INTERFACE, এবং Python-এর HTTP ক্লায়েন্টে source_address আর্গুমেন্ট। ঠিকানাটি টানেলেরই হতে হবে, আর এখানে একটি টাইপো রাউটিং অভিযোগ নয়, সাধারণ সংযোগ ত্রুটি হিসেবে ব্যর্থ হয়। ঠিকানার বদলে ডিভাইসের নামে বাঁধা Linux-এর একটি বিশেষাধিকার এবং Windows বা macOS-এ বহনযোগ্য নয়, তাই পোর্টেবল স্ক্রিপ্টগুলো ঠিকানাই দেয়।
Linux: মালিককে মেলান, প্রোগ্রামের নাম নয়
কার্নেল সকেটের মালিক ব্যবহারকারীকে মেলাতে পারে, তাই একটি নির্দিষ্ট অ্যাকাউন্টই নির্বাচক হয়ে ওঠে। টানেলটিকে নিজের রাউটিং টেবিলে রাখুন এবং কেবল সেই অ্যাকাউন্টের ট্রাফিক সেখানে পাঠান:
ip rule add uidrange 1001-1001 lookup 100
ip route add default dev tun0 table 100এটি কেবল তখনই কাজ করে যদি টানেলটি আগে থেকেই ডিফল্ট রুট না হয়। ক্লায়েন্টটি --route-nopull দিয়ে চালু করুন, অথবা --pull-filter ignore "redirect-gateway" দিয়ে OpenVPN 2.4 ও পরের সংস্করণে পুশ করা রিডাইরেক্ট ফিল্টার করুন, তারপর টেবিল এন্ট্রিটি নিজেই যোগ করুন। নাহলে দুই ডিফল্টই থাকে এবং কম অগ্রাধিকারেরটি জিতে যায়। এর ইচ্ছাকৃত সংস্করণ হলো ডিফল্ট রুটটি টানেল গেটওয়ের দিকে দেখানো, বা কোনও ডিসকানেক্ট স্ক্রিপ্ট থেকে তা করা: টানেল পড়ে গেলে এটি ট্রাফিক ইন্টারনেটের বাইরে রাখে, কিন্তু এটি একটি কিল সুইচ, নির্বাচক নয়, এবং কেউ সরানো পর্যন্ত থাকে।
আরও শক্তিশালী সংস্করণ হলো নেটওয়ার্ক নেমস্পেস। এটি তৈরি করুন, ক্লায়েন্টকে ভেতরে চালান এবং অ্যাপটি ip netns exec দিয়ে শুরু করুন। নেমস্পেসের বাইরে কিছুই টানেল ইন্টারফেস দেখে না, তাই অ্যাপটি ভুল করে হোস্ট রুটে ফিরে যেতে পারে না। খরচটি বাস্তব: নেমস্পেসের ভৌত নেটওয়ার্কে পৌঁছাতে একটি veth জোড়া এবং নিজস্ব রিজলভার ফাইল দরকার, কারণ মাউন্ট নেমস্পেস হোস্টের রিজলিউশন সেটিংস ভাগ করে না।
দুটি ব্যর্থতার ধরন বেশিরভাগ লিক ব্যাখ্যা করে। নাম রিজলিউশন এখনও হোস্টের রিজলভারে ঘটে, তাই TCP সংযোগ ভেতরে গেলেও কোয়েরিটি টানেলের বাইরে বেরোয়। আর IPv6-এর জন্য নিজস্ব ip -6 rule দরকার, কারণ v4 নিয়ম এটি ঢাকে না।
Windows ও macOS: ফায়ারওয়াল সীমিত করুন বা প্রক্সি দিন
Windows-এও অ্যাপভিত্তিক রুট নির্বাচক নেই, তবে তার ফায়ারওয়াল নিয়ম প্রোফাইলে সীমিত করা যায়, আর VPN অ্যাডাপ্টারকে „পাবলিক“ হিসেবে শ্রেণিবদ্ধ করা যায় যখন বাড়ির LAN „প্রাইভেট“ থাকে। „প্রাইভেট“ ও „ডোমেইন“ প্রোফাইলে প্রোগ্রামটির জন্য আউটবাউন্ড ব্লক নিয়ম তৈরি করুন, „পাবলিক“ খোলা রাখুন, তাহলে অ্যাপটি কেবল টানেল দিয়েই নেটওয়ার্কে পৌঁছাতে পারবে। প্রথমে সংযোগের নেটওয়ার্ক শ্রেণি নির্ধারণ করুন, তারপর ফায়ারওয়ালের মনিটর প্যানেলে নিয়মটি মিলেছে কি না নিশ্চিত করুন। এটি ব্লক, রুট নয়: টানেল পড়ে গেলে এটি বন্ধ হয়ে ব্যর্থ হয়, আর ফায়ারওয়াল উপেক্ষা করা অ্যাপকে থামাতে পারে না।
macOS ভিন্ন টুলে একই যুক্তি অনুসরণ করে। SSH দিয়ে পৌঁছানো SOCKS লিসেনারই সবচেয়ে কম হস্তক্ষেপের বিকল্প, কারণ ssh -D 1080 user@host একটি স্থানীয় পোর্ট তৈরি করে এবং কেবল সেটি ব্যবহারে কনফিগার করা অ্যাপগুলোই ওই পথ নেয়। একটি pf নিয়ম ব্যবহারকারী বা গ্রুপ অনুযায়ী প্রোগ্রাম ব্লক করতে পারে, কিন্তু কেবল সেই প্রোগ্রামের জন্য টানেল বেছে নিতে পারে না। Tunnelblick ডোমেইন অনুযায়ী ফিল্টারই করতে পারে না, কারণ রুটগুলো ঠিকানা নিয়ে কাজ করে; OpenConnect ব্যবহারকারীরা vpn-slice ব্যবহার করেন যখন সার্ভার খুব প্রশস্ত প্রিফিক্স তালিকা পুশ করে।
দুটি বিকল্প প্ল্যাটফর্ম টুলগুলো যা মিস করে তা ঢেকে দেয়। ডোমেইন অনুযায়ী প্রক্সি বদলানো ব্রাউজার এক্সটেনশনই „কেবল এই সাইট“-এর ব্যবহারিক উত্তর, কারণ তারা অ্যাপ স্তরে কাজ করে যেখানে নাম এখনও আছে; তারা একটি ব্রাউজার ঢাকে, আর কিছুই না। মোটা সমাধান হলো বিক্রেতার ক্লায়েন্টকে ভার্চুয়াল মেশিনে চালানো, যাতে গেস্ট রুট পরিবর্তনের মালিক হয় আর হোস্ট নিজের পথ ধরে রাখে, যার খরচ দুটি নেটওয়ার্ক স্ট্যাক রক্ষণাবেক্ষণ।
বাঁধার বিকল্প নেই এমন টুলের জন্য SOCKS হপ
বেশিরভাগ অ্যাপকে HTTP বা SOCKS প্রক্সিতে পাঠানো যায়, যদিও তারা ইন্টারফেস সম্পর্কে কিছুই জানে না। একটি SSH ক্লায়েন্ট সার্ভার দিকে কোনো পরিবর্তন ছাড়াই একটি দেয়:
ssh -N -D 127.0.0.1:9999 [email protected]ওই পোর্ট ব্যবহারে কনফিগার করা সবকিছুই ব্যাস্টিয়নে বেরোয়, আর ডেস্কটপের বাকি অংশ নিজের স্বাভাবিক সংযোগ ধরে রাখে। টানেলের ঠিকানায় বাঁধা স্থানীয় 3proxy ইনস্ট্যান্স SSH ছাড়াই একটি পোর্টের জন্য একই কাজ করে, আর stunnel একটি TLS-মোড়া পোর্ট ঢাকে। সীমাবদ্ধতাগুলো বাস্তব: প্রক্সি কেবল এমন প্রোটোকল বহন করে যা এটি ব্যবহার করতে জানে, UDP-এর জন্য SOCKS5 UDP অ্যাসোসিয়েশন দরকার যা অনেক ক্লায়েন্টের „SOCKS5“ সেটিং চালু করে না, আর DNS প্রক্সি দিয়ে রিজলভ করতে হবে (curl-এ socks5h), নাহলে নামটি স্থানীয় রিজলভারে চলে যায়।
অন্য মেশিনের দরকার না থাকলে লিসেনারটিকে লুপব্যাকে রাখুন।
স্টেটাস বার্তা নয়, ক্যাপচার দিয়ে পথটি যাচাই করুন
অ্যাপের স্টেটাস উইন্ডো সেটিংটির বর্ণনা দেয়, প্যাকেটের নয়। দুই ইন্টারফেসেই ক্যাপচার করুন এবং তুলনা করুন:
tcpdump -i any -n port 443
ss -tnp | grep 443সংযোগটি ভৌত ইন্টারফেসে দেখা গেলে এবং টানেলে মিলে যাওয়া এন্ট্রি না থাকলে, ক্লায়েন্ট যা-ই রিপোর্ট করুক, নির্বাচকটি কার্যকর নয়। IPv6 ও রিজলভার আলাদাভাবে পরীক্ষা করুন, আর সহায়ক প্রক্রিয়া বা উত্তরাধিকারে পাওয়া সিস্টেম প্রক্সির মতো দ্বিতীয় কোনো পথ আছে কি না দেখুন।
তারপর প্রয়োজনের সঙ্গে মেলা সীমাটি বাছুন: একটি সাইটের জন্য প্রক্সি যথেষ্ট, একটি অ্যাকাউন্টের জন্য UID নিয়ম যথেষ্ট, সংযোগটি বন্ধ হয়ে ব্যর্থ হওয়া দরকার হলে ফায়ারওয়াল প্রোফাইল যথেষ্ট, আর নেমস্পেসই একমাত্র বিকল্প যা টানেলকে বাইরের সবকিছু থেকে লুকায়।