گاهی لازم است ترافیک یک برنامه از دروازهٔ دیگری خارج شود یا قواعد مسیریابی مشخصی روی آن اعمال شود، اما خود برنامه گزینهای برای تنظیم Mark ندارد. تغییر کد و ساخت دوبارهٔ آن هم همیشه ممکن یا بهصرفه نیست.
پروژهٔ Socket Marker یک کتابخانهٔ اشتراکی کوچک به زبان C است که با LD_PRELOAD بارگذاری میشود و روی سوکتهای ساختهشده از طریق تابع socket()، گزینهٔ SO_MARK را تنظیم میکند. سپس میتوان از این علامت در قواعد مسیریابی لینوکس استفاده کرد؛ برای مثال، برای انتخاب مسیر در شبکههای چنددروازهای یا هدایت ترافیک به یک VPN از قبل پیکربندیشده.
روش کار
کتابخانه هنگام بارگذاری، آدرس تابع اصلی socket() را با dlsym پیدا میکند. هر بار برنامه این تابع را از مسیر قابلجایگزینی libc فراخوانی کند، ابتدا سوکت واقعی ساخته میشود و سپس کتابخانه روی آن setsockopt را اجرا میکند:
setsockopt(fd, SOL_SOCKET, SO_MARK, &socket_mark, sizeof(socket_mark));
مقدار Mark یک بار هنگام بارگذاری از متغیر محیطی SO_MARK خوانده میشود. اگر این متغیر وجود نداشته باشد یا خالی باشد، مقدار پیشفرض ۱۲۳۴ استفاده میشود. تغییر متغیر محیطی پس از مقداردهی اولیه، مقدار ذخیرهشده در کتابخانه را عوض نمیکند.
خود کتابخانه جدول مسیریابی یا تونل ایجاد نمیکند. طبق مستندات سوکت لینوکس، SO_MARK علامت بستههای ارسالی از سوکت را تعیین میکند؛ انتخاب مسیر بر اساس آن به قواعد شبکهٔ میزبان وابسته است.
پیشنیازها و ساخت
برای ساخت به Linux، کامپایلر GCC، Make و فایلهای توسعهٔ libc نیاز دارید. برنامهٔ هدف باید پیوند پویا داشته باشد و از libc سازگار با LD_PRELOAD، مانند glibc، استفاده کند. قابلیت SO_MARK از کرنل نسخهٔ ۲٫۶٫۲۵ در دسترس است.
تنظیم Mark به قابلیت CAP_NET_ADMIN نیاز دارد؛ از کرنل نسخهٔ ۵٫۱۷، قابلیت CAP_NET_RAW نیز برای این عملیات پذیرفته میشود. نمونههای زیر از sudo استفاده میکنند، بنابراین برنامهٔ هدف و کتابخانه با دسترسی کاربر ریشه اجرا میشوند و باید مورداعتماد باشند.
ابتدا مخزن را دریافت و کتابخانه را بسازید:
git clone https://github.com/hatamiarash7/socket-marker.git
cd socket-marker
make
خروجی، فایل setmark.so در دایرکتوری مخزن است. Makefile پروژه آن را بهصورت Shared Library با گزینهٔ -fPIC و اتصال به libdl میسازد. برای استفادهٔ محلی، نصب سراسری لازم نیست.
اجرای برنامه با Mark دلخواه
از دایرکتوری مخزن، برای اجرای curl با Mark برابر ۱۰۰:
sudo SO_MARK=100 LD_PRELOAD="$PWD/setmark.so" curl https://example.com
نشانی example.com صرفاً مقصد نمونه است. اگر SO_MARK را حذف کنید، مقدار پیشفرض کتابخانه استفاده میشود:
sudo LD_PRELOAD="$PWD/setmark.so" curl https://example.com
مقدار را به شکل عدد صحیح دهدهی بدون علامت، در بازهٔ 0 تا 4294967295 وارد کنید. در پیادهسازی فعلی، مبنای تبدیل ده است؛ بنابراین مقداری مثل 0x64 برای نمایش هگزادسیمال پذیرفته نمیشود. ورودیهایی مثل abc یا 4294967296 نیز باعث ثبت هشدار در stderr و استفاده از مقدار پیشفرض میشوند.
برای نصب اختیاری در مسیر پیشفرض /usr/local/lib/setmark.so:
sudo make install
sudo SO_MARK=100 LD_PRELOAD=/usr/local/lib/setmark.so curl https://example.com
برای حذف نسخهٔ نصبشده با همین مسیر پیشفرض، در دایرکتوری مخزن دستور sudo make uninstall را اجرا کنید.
نمونهٔ مسیریابی مبتنی بر Mark
فرض کنید میزبان روی اینترفیس eth0 به شبکهٔ 192.168.1.0/24 متصل است و دروازهٔ موردنظر 192.168.1.1 است. این نام و آدرسها نمونهاند و باید با شبکهٔ واقعی جایگزین شوند. ابزارهای این بخش از مجموعهٔ iproute2 هستند.
در این مثال از جدول شمارهٔ ۱۰۰ و اولویت ۱۰۰۰۰ استفاده میکنیم. ابتدا مطمئن شوید این جدول برای کار دیگری استفاده نمیشود و اولویت انتخابشده با قواعد موجود تداخل ندارد. این دستورها تنظیمات مسیریابی میزبان را تغییر میدهند:
sudo ip -4 route add 192.168.1.0/24 dev eth0 scope link table 100
sudo ip -4 route add default via 192.168.1.1 dev eth0 table 100
sudo ip -4 rule add priority 10000 fwmark 100 table 100
مسیر اول، شبکهٔ متصل را در جدول جدید معرفی میکند؛ مسیر دوم دروازهٔ پیشفرض آن جدول است. قانون سوم بستههای دارای Mark برابر ۱۰۰ را برای جستوجوی مسیر به این جدول میفرستد. استفاده از شمارهٔ جدول نیاز به افزودن نام در rt_tables ندارد.
اکنون از دایرکتوری پروژه، درخواست نمونه را با IPv4 اجرا کنید:
sudo SO_MARK=100 LD_PRELOAD="$PWD/setmark.so" curl -4 https://example.com
این نمونه فقط IPv4 را پوشش میدهد. قواعد IPv6 باید جداگانه تنظیم شوند. همچنین ترتیب قواعد مسیریابی اهمیت دارد: عدد اولویت کوچکتر زودتر بررسی میشود و قواعد مقدم، از جمله مسیرهای محلی، میتوانند پیش از قانون نمونه نتیجه بدهند.
برای مشاهدهٔ قواعد و جدول:
ip -4 rule show
ip -4 route show table 100
ip -4 route get 192.0.2.10 mark 100
آدرس 192.0.2.10 در دستور آخر یک مقصد آزمایشی است. دستور ip route get تصمیم مسیریابی برای Mark دادهشده را نشان میدهد و بستهای ارسال نمیکند؛ این خروجی بهتنهایی ثابت نمیکند که سوکت برنامه واقعاً علامتگذاری شده است.
پس از آزمایش، برای حذف همان قانون و مسیرهای نمونه:
sudo ip -4 rule del priority 10000 fwmark 100 table 100
sudo ip -4 route del default via 192.168.1.1 dev eth0 table 100
sudo ip -4 route del 192.168.1.0/24 dev eth0 scope link table 100
بررسی اجرا و محدودیتها
هنگام مقداردهی اولیهٔ موفق کتابخانه با Mark برابر ۱۰۰، پیام زیر در stderr نوشته میشود:
[Socket Marker] Initialized (mark=100)
این پیام بارگذاری کتابخانه را نشان میدهد، نه موفقیت علامتگذاری همهٔ سوکتها. اگر setsockopt شکست بخورد، کتابخانه خطا را ثبت میکند، مقدار قبلی errno را برمیگرداند و همان سوکت معتبر را به برنامه تحویل میدهد. بنابراین شکست علامتگذاری مانع ادامهٔ ارتباط نمیشود و نباید این ابزار را تضمینی برای جلوگیری از خروج ترافیک از مسیر دیگر دانست.
محدودهٔ کار کتابخانه نیز مهم است:
- باینریهای Static پشتیبانی نمیشوند. این روش به بارگذاری و جایگزینی نمادها توسط Dynamic Linker وابسته است.
- فقط فراخوانیهای قابلرهگیری
socket()پوشش داده میشوند. سوکتهای از قبل ساختهشده یا دریافتشده از فرایندی دیگر، و فراخوانی مستقیم System Call که libc را دور میزند، از این مسیر عبور نمیکنند. - برنامههای دارای امتیاز ویژه محدودیت بارگذاری دارند. طبق مستندات Dynamic Linker، در حالت Secure Execution مانند اجرای setuid/setgid یا دریافت قابلیت از فایل اجرایی،
LD_PRELOADمحدود میشود. افزودن قابلیت باsetcapبهتنهایی راهحل عمومی برای اجرای این کتابخانه نیست. - پشتیبانی بومی برنامه را بررسی کنید. برای نمونه،
pingاز مجموعهٔ iputils گزینهٔ-mوmtrگزینهٔ-Mرا برای Mark دارند؛ در چنین مواردی نیازی به افزودن این کتابخانه نیست.
Socket Marker برای آزمایش و اعمال مسیریابی مبتنی بر Mark روی برنامههای سازگار، بدون تغییر کد آنها، کاربرد دارد. کد پروژه با مجوز MIT منتشر شده و مثالهای بیشتر در راهنمای مخزن در دسترس است.