چندین سال قبل که از پروتون ویپیان (Proton VPN) استفاده میکردم، یک ویژگی جذاب به اسم Split Tunneling داشت که اجازه میداد فقط یک برنامه خاص از ویپیان استفاده کنه و کاری به برنامههای دیگر نداشته باشه. اما خیلی برام عجیب بود که این ویژگی در لینوکس وجود نداشت (یا من بلد نبودم). الان مدتی هست که دیگه از ویپیانی استفاده نمیکنم (چون کار نمیکنن) ولی بالاخره یه راهی پیدا کردم این امکان رو در لینوکس فراهم میکنه.
این مقاله کمی پیچیده هست و نیازمند اینه که شما آشنایی جزیی با مطالب زیر داشته باشید. البته اگه باهاشون آشنا نیستید هم من همهی دستورات رو تا جای ممکن توضیح میدم که بتونید اجراشون کنید.
نکتهی خیلی مهم: تمام دستورات این مقاله روی کرنل با نسخه ۶.۱ اجرا شده و ممکنه برای شما متفاوت باشه یا کار نکنه (دستورات مربوط cgroup).
# My kernel version: $ uname -msr Linux 6.1.55-1-MANJARO x86_64
تمام مراحل این مقاله رو میتونید تو اسکریپتی که نوشتم پیدا کنید.
شرح مسئله
با فرض وجود یک پروکسی SOCKS روی آدرس 127.0.0.1:2080، ترافیک پردازه P1 رو بطوری کنترل کنیم که به دست پروکسی برسه و بعد هم به اینترنت. شکل زیر به خوبی نشون میده این هدف این مقاله این چیه:
هدف این مقاله پیدا کردن راه ارتباطی بین پردازه ۱ (P1) و پروکسی (علامت سوال در عکس) هستش.
کنترل گروپ، یا به اختصار cgroup (بخونید: سی گروپ)، یک ویژگی در کرنل لینوکس است که به ما امکان کنترل منابع رو میده. برای مثال پردازههای ۲ و ۳ فقط ۵۱۲ مگابایت از حافظه رم برخوردار بشن. این ویژگی از یکسری گروه و یکسری کنترلر تشکیل شده. ما میتونیم برای هر گروه کنترلر خاصی رو اعمال کنیم. برای مثل به گروه ۱ که شامل پردازهها ۲ و ۳ هست، کنترلر حافظه رم رو اختصاص میدیم که استفاده رم این دو پردازه رو محدود کنه. اطلاعات بیشتر در خصوص این ویژگی رو میتونید تو راهنمای خود لینوکس بخونید.
نکته: این مقاله از cgroup نسخه ۱ استفاده میکنه. اما اگه میخواید از cgroup v2 استفاده کنید دقت کنید توی iptables وقتی که از ماژول cgroup استفاده میکنید باید از فلگ path-- استفاده کنید نه cgroup--.
قدم اول، ایزوله کردن ترافیک یک پردازه هستش. اگه بتونیم تشخیص بدیم پکتهای خروجی مربوط به کدوم پردازه هستش، میتونیم با ابزارهای مسیریابی لینوکس (iproute2) منتقلش کنیم به پروکسی. این امکان رو کنترلر net_cls یا Network Classifier به ما میده. این کنترلر، به پکتهای هر پردازهای که توی گروهش باشه، یه عدد میچسبونه به اسم Network Classifier ID یا classid که یک عدد ۳۲ بیتی بدون علامته. بعدا با استفاده از این عدد میتونیم توی Netfilter قوانینی بذاریم که فقط روی پکتهایی اعمال شه که این عدد رو دارن (در ادامه توضیح میدم). خب بیاید این رو پیاده کنیم.
اول باید مطمئن شیم که net_cls روی فایل سیستم ما مونت شده (mounted) یا نه که میتونیم با دستور زیر بررسی کنیم:
$ mount -t cgroup net_cls on /sys/fs/cgroup/net_cls type cgroup (rw,relatime,net_cls)
که اگه در خروجی دستور بالا، net_cls رو ندیدید یعنی این کنترلر مونت نیست و باید مونتش کنید:
sudo mkdir -p /sys/fs/cgroup/net_cls sudo mount -t cgroup -o net_cls net_cls /sys/fs/cgroup/net_cls
حالا ما امکان استفاده از net_cls رو داریم و باید یک گروه جدید برای این کنترلر بسازیم. چون cgroup هم نوعی فایل سیستم هستش، با ساختن یک دایرکتوری میتونیم گروه بسازیم. گروه susgp (مخفف suspicious group) رو میسازیم و بهش classid میدیم:
sudo mkdir /sys/fs/cgroup/net_cls/susgp cd /sys/fs/cgroup/net_cls/susgp echo -n "0x10f2c" | sudo tee net_cls.classid
خب حالا هر پردازهای توی گروه susgp باشه، به پکتهاش عدد 0x10f2c چسبیده میشه.
نکته: عدد 0x10f2c یک عدد فرضی هستش. شما هر عدد ۳۲ بیتی دیگهای رو میتونید بکار ببرید.
حالا باید بیایم این پکتها رو هدایت کنیم. سوال اینه که به کجا؟ که در جواب باید بگم ما یک اینترفیس tun میسازیم و تمام پکتهارو میفرستیم بهش. بعدش باید از این اینترفیس tun پکتهارو بخونیم و بفرستیمش به سمت پروکسی. برای ساخت یک اینترفیس tun از ماژول tuntap دستور ip استفاده میکنیم:
# Example: ip tuntap add mode tun dev <name> sudo ip tuntap add mode tun dev susif
نکته: اسم اینترفیس هم میتونه هرچی باشه و شما مجبور نیستید مثل من بامزه باشید و اسمش رو susif (مخفف suspicious interface) بذارید :). مثلا اسمش رو بذارید: myboringiface :|.
در مرحله بعد باید به اینترفیس جدیدمون یک آیپی بدیم و بیاریمش بالا:
# Example: ip addr add <ip>/<subnet> dev <name> sudo ip addr add 10.20.30.40/24 dev susif sudo ip link set dev susif up
حالا باید پکتهای خروجی از پردازههای موجود در گروه susgp رو به کمک ساخت جدول مسیریابی جدید به اینترفیس susif هدایت کنیم (چرا جدول جدید؟ با تغییر قوانین جدول اصلی، پکتهای کل سیستم بدست اینترفیس tun میرسه که ما این رو نمیخوایم. به جاش با ساخت جدول جدید فقط برای پکتهای گروه susgp قوانین مسیریابیی متفاوتی رو استفاده میکنیم). باید توجه کنیم که در مسیریابی امکان استفاده از classid نیست، و باید به کمک iptables، این عدد رو تبدیل کنیم. برای انجام اینکار، پکتها رو مارک میکنیم و قانونی میذاریم که تمامی این پکتهای مارک شده از جدول مسیریابی جدید استفاده کنن. اول یه جدول مسیریابی جدید به اسم susrt (مخفف suspicious routing table) میسازیم:
echo "1234 susrt" | sudo tee -a /etc/iproute2/rt_tables
نکته: عدد ۱۲۳۴ آیدی جدول مسیریابی هست که شما هر عدد دیگهای هم میتونید بذارید. اما دستورات آیپی با اسم هم کار میکنن. بنابراین ما فقط در این قسمت عدد رو مینویسیم و دیگه کاری بهش نداریم و با susrt تمام دستورات دیگه رو اجرا میکنیم.
نکته: دقت کنید که کامند بالا رو یکبار اجرا کنید و اگه فایل مورد نظر وجود نداشت، بسازیدش:
sudo mkdir /etc/iproute2 sudo touch /etc/iproute2/rt_tables
حالا برای این جدول جدید یک مسیر پیشفرض تعریف میکنیم:
sudo ip route add default via 10.20.30.1 table susrt
حالا با کمک ip rule به پکتهای مارک شده میگیم که از جدول susrt برای مسیریابی خودشون استفاده کنن:
sudo ip rule add fwmark 20 table susrt
حالا ما یک جدول مسیریابی جدید داریم که پکتهایی که از این جدول استفاده میکنن به دست اینترفیس susif میرسن و فقط باید classid رو تبدیل به fwmark یا firewall mark کنیم. ما مارک رو عدد ۲۰ در نظر گرفتیم پس:
sudo iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x10f2c -j MARK --set-mark 20
این دستور یک قانون جدید توی جدول mangle اضافه میکنه که با استفاده از ماژول cgroup، میایم به پکتهایی که کلاس آیدیشون 0x10f2c هستش، مارک ۲۰ رو میچسبونیم. این قانون در OUTPUT chain اضافه شده که مربوط به پکتهای خروجی از پردازه لوکال هستش (برای مطالعه بیشتر در خصوص netfliter برید اینجا و اینجا).
شکل زیر کارهای سادهای هست که تا اینجا انجام دادیم. پکتهایی خروجی از پردازههایی که در گروه susgp هستن، کلاس آیدیشون 0x10f2c دارن. این پکتها در زنجیر OUTPUT از netfilter فیلتر شده و بهشون مارک ۲۰ چسبونده میشه. حالا دوباره قوانین مسیریابی روی پکت انجام میشه و اینترفیس خروجی این پکتها به اینترفیس susif (که خودمون ساختیم) تغییر پیدا میکنه.
حالا تنها قسمت باقی مونده خوندن این پکتها از روی اینترفیس susif و فرستادن اونها به سمت پروکسی هستش. قبل از هر کاری باید دقت کنیم که اینترفیس tun تو لایه IP کار میکنه و پکتهایی که از روی این اینترفیس خونده میشه پکتهای IP هستند. اما پروکسی ساکس در لایههای بالاتر کار میکنه (لایه اپلیکیشن). بنابراین ما امکان ارسال مستقیم پکتهای IP به این پروکسی رو نداریم. در این مرحله دو گزینه پیش روی ماست:
برای نصب tun2socks دستورات زیر رو اجرا میکنیم: (اگر به مشکل خوردید به اینجا مراجعه کنید):
git clone https://github.com/xjasonlyu/tun2socks.git cd tun2sokcs make tun2socks sudo cp ./build/tun2socks /usr/local/bin
پس از نصب tun2socks، کافیه که دستور زیر رو اجرا کنیم:
tun2socks -device susif -proxy socks5://127.0.0.1:2080 -interface lo
دستور بالا کارهای زیر رو برای ما انجام میده:
شکل زیر به صورت ساده نشون میده که تو سیستم ما چه اتفاقی میافته:
برای تست این ویژگی کافیه آیدی پردازه مورد نظر رو به فایل cgroup.procs اضافه کنیم.
echo 1515 | sudo tee -a /sys/fs/cgroups/net_cls/susgp/cgroup.procs
حالا تمام ترافیک این پردازه از پروکسی رد میشه.
# $$ holds the pid of the current process. in this example it will be the pid of the shell we are in. echo $$ | sudo tee -a /sys/fs/cgroups/net_cls/susgp/cgroups.procs
برای تست اینکه ترافیک ترمینال واقعا از پروکسی رد میشه، میتونیم آیپی پروکسی رو ببینیم:
curl https://api.ipify.org 1.2.3.4
# Notice that the path has changed echo 1515 | sudo tee -a /sys/fs/cgroups/net_cls/cgroup.procs # Remove the current shell from susgp group. Again notice the path change echo $$ | sudo tee -a /sys/fs/cgroups/net_cls/cgroups.procs
pidof dockerd | sudo tee -a /sys/fs/cgroups/net_cls/susgp/cgroups.procs
بعد از این میتونید با خیال راحت ایمیجهای داکر رو پول کنید :).