Sina Shabani
Sina Shabani
خواندن ۹ دقیقه·۸ ماه پیش

ایزوله کردن ترافیک یک پردازه به کمک cgroups در لینوکس

مرد درون جعبه قرمز نمایانگر یک پردازه لینوکسی ایزوله شده است
مرد درون جعبه قرمز نمایانگر یک پردازه لینوکسی ایزوله شده است

پیش‌گفتار

چندین سال قبل که از پروتون وی‌پی‌ان (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--.

کنترلر net_cls

قدم اول، ایزوله کردن ترافیک یک پردازه هستش. اگه بتونیم تشخیص بدیم پکت‌های خروجی مربوط به کدوم پردازه هستش، می‌تونیم با ابزارهای مسیریابی لینوکس (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 &quot0x10f2c&quot | sudo tee net_cls.classid

خب حالا هر پردازه‌ای توی گروه susgp باشه، به پکت‌هاش عدد 0x10f2c چسبیده می‌شه.

نکته: عدد 0x10f2c یک عدد فرضی هستش. شما هر عدد ۳۲ بیتی دیگه‌ای رو میتونید بکار ببرید.

ساخت اینترفیس tun و جدول مسیریابی جدا

حالا باید بیایم این پکت‌ها رو هدایت کنیم. سوال اینه که به کجا؟ که در جواب باید بگم ما یک اینترفیس 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 &quot1234 susrt&quot | 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
شکل ساده از مسیر پکت در سیستم و خروج از اینترفیس susif

حالا تنها قسمت باقی مونده خوندن این پکت‌ها از روی اینترفیس susif و فرستادن اونها به سمت پروکسی هستش. قبل از هر کاری باید دقت کنیم که اینترفیس tun تو لایه IP کار می‌کنه و پکت‌هایی که از روی این اینترفیس خونده می‌شه پکت‌های IP هستند. اما پروکسی ساکس در لایه‌های بالاتر کار می‌کنه (لایه اپلیکیشن). بنابراین ما امکان ارسال مستقیم پکت‌های IP به این پروکسی رو نداریم. در این مرحله دو گزینه پیش روی ماست:

  1. خوندن دستی پکت‌ها از روی اینترفیس و هندل کردن تمام حالات متفاوت (مثل TCP handshake) و ایجاد ارتباط با پروکسی ساکس.
  2. استفاده از tun2socks که تمام این حالات رو هندل کرده و فقط نیاز به نصب این ابزار داریم.

برای نصب 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

دستور بالا کارهای زیر رو برای ما انجام می‌ده:

  1. از روی اینترفیس susif پکت‌ها رو می‌خونه
  2. پکت‌های IP خونده شده رو باز می‌کنه و تغییرات لازم را اعمال می‌کنه
  3. بعنوان یک کلاینت socks5 به پروکسی ما روی آدرس 127.0.0.1:2080 متصل می‌شه
  4. و پکت‌های باز شده رو به سمت پروکسی می‌فرسته

شکل زیر به صورت ساده نشون می‌ده که تو سیستم ما چه اتفاقی می‌افته:

شکل نهایی از مسیر پکت از زمان خروج از پردازه تا زمان رسیدن به پروکسی
شکل نهایی از مسیر پکت از زمان خروج از پردازه تا زمان رسیدن به پروکسی

تست و بررسی

برای تست این ویژگی کافیه آی‌دی پردازه مورد نظر رو به فایل cgroup.procs اضافه کنیم.

  • برای مثال پردازه با آیدی ۱۵۱۵ رو می‌خوایم به گروه susgp اضافه کنیم:
echo 1515 | sudo tee -a /sys/fs/cgroups/net_cls/susgp/cgroup.procs

حالا تمام ترافیک این پردازه از پروکسی رد میشه.

  • یا حتی می‌تونیم ترمینالی که باهاش کار می‌کنیم هم به گروه susgp اضافه کنیم. برای انجام این کار تو ترمینالی که میخواید پروکسی شه، دستور زیر رو بزنید:
# $$ 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
  • یا حتی می‌تونیم داکر رو تونل کنیم. منتها نکته‌ای وجود داره اینه که شما نمی‌تونید دستور داکر رو بندازید تو گروه susgp و انتظار داشته باشید کار کنه. داکر فقط کلاینت api هستش و پردازه‌ای که واقعا کارهارو انجام می‌ده dockerd هستش. برای همین باید dockerd رو بیاریم توی گروه susgp:
pidof dockerd | sudo tee -a /sys/fs/cgroups/net_cls/susgp/cgroups.procs

بعد از این می‌تونید با خیال راحت ایمیج‌های داکر رو پول کنید :).

منابع

  1. Script of all the commands in this article
  2. iptables and cgroups v2 (netfilter&amp;#x27;s xt_cgroup)
  3. How to bypass OpenVPN per application
  4. novpn: Bypass VPN for specific apps [Linux / OpenVPN]
  5. cgroups v1
  6. Netfilter


لینوکسcgroupsشبکهپروکسیnetwork
شاید از این پست‌ها خوشتان بیاید