زنجیرهکردن دو کلاینت OpenVPN روی یک میزبان Linux با fwmark
هر دو کلاینت مسیر پیشفرض را میخواهند. سرور درونی را با یک مسیر میزبان سنجاق کنید، جلوی بازنویسی جدولها توسط کلاینت دوم را بگیرید و سپس جریانها را با fwmark هدایت کنید.
چیدن یک کلاینت OpenVPN دوم روی یک میزبان Linux در یکی از دو جا شکست میخورد. یا کلاینت درونی به سرور خودش نمیرسد چون تونل بیرونی آن انتقال را گرفته است، یا کلاینت دوم جدول مسیرها را بازنویسی میکند و تونل اول از کار میافتد. هر دو مشکل در مسیریابی است، نه در رمزنگاری، و هر دو پیش از آنکه تونل جدید ترافیک برنامهای حمل کند حل میشوند. نشانیهای اینجا نمونهاند: tun0 تونل بیرونی است، 203.0.113.9 سرور درونی است و TCP 9999 جریانی است که باید از مسیر درونی استفاده کند.
دو حالت شکست، یک جدول مسیر
کلاینت درونی یک سوکت UDP به سرور خود باز میکند و آن سوکت مانند هر سوکت دیگری از جدول مسیر جاری پیروی میکند. اگر تونل بیرونی از قبل مالک مسیر پیشفرض باشد، بستههای دستدادن به تونل بیرونی میروند. این فقط وقتی کار میکند که سرور بیرونی بتواند به سرور درونی برسد و طرف مقابل حمل جریان را بپذیرد؛ وگرنه دستدادن متوقف میشود و کلاینت دوباره تلاش میکند.
شکست دوم از push سرور میآید. push معمول redirect-gateway def1 است که بهجای جایگزینی مسیر پیشفرض، 0.0.0.0/1 و 128.0.0.0/1 را نصب میکند. بنابراین دو کلاینتی که هرکدام جفت /1 خود را نصب میکنند با هم تصادم میکنند و جدیدترین جفت تقریباً برای هر مقصدی برنده میشود. وقتی تونلی پس از اتصال دومی میمیرد، آن دو پیشوند را در جدول بجویید، چون ممکن است خود مسیر پیشفرض هرگز تغییر نکرده باشد.
جریان را علامت بزنید و به آن جدول بدهید
ترافیک انتخابی را با علامتزدن آن در جدول mangle و تطبیق آن علامت در یک قاعده سیاستی هدایت کنید. قاعده به جدولی اختصاصی اشاره میکند که مسیر پیشفرضش دستگاه درونی است، پس فقط بستههای علامتخورده مسیر درونی را میگیرند.
iptables -t mangle -A PREROUTING -p tcp -m multiport --dport 9999 -j MARK --set-mark 0x9999
ip rule add fwmark 0x9999 table 200 pref 1000
ip route add default dev tun1 table 200بیشتر شکستها در این مجموعه قاعده از دو اشتباه میآید. مقدار علامتی که دیوار آتش میگذارد و مقداری که قاعده تطبیق میدهد باید یکسان باشند، چون عدم تطابق نه خطا میدهد نه اثر. و PREROUTING فقط بستههایی را میبیند که از بیرون میرسند، بنابراین اتصالی که روی این میزبان آغاز میشود به یک قاعده OUTPUT با همان تطبیق نیاز دارد تا اصلاً علامت بخورد.
مالکیت مسیرها را از کلاینت دوم بگیرید
دو گزینه مانع ویرایش جدول توسط کلاینت دوم میشوند و در نقاط مختلف عمل میکنند. route-nopull گزینههای مسیرِ pushشده را نادیده میگیرد، پس چیزی نصب نمیشود. route-noexec گزینههای pushشده را در محیط کلاینت میپذیرد اما تغییر جدول را به یک hook میسپارد، که دقیقاً همان چیزی است که وقتی باید ببینید سرور چه پیشنهاد داده میخواهید.
hook با مقادیر pushشده در محیط خود اجرا میشود و باید فقط ورودیهایی را اضافه کند که تأیید کردهاید. آن را idempotent کنید: جدولی را که مالک آن است پیش از بازسازی خالی کنید و فرض نکنید اجرای قبلی وضعیت پاکی برجای گذاشته است. اتصال دوباره hook را دوباره فرا میخواند و hookی که بدون پاکسازی اضافه میکند مسیرهای تکراری انباشته میکند.
یک مالک انتخاب کنید و آن را بنویسید. پروفایلی که هر دو گزینه را تنظیم میکند، یا hookی که با یک اسکریپت دستی رقابت میکند، جدولی میسازد که بعداً هیچکس نمیتواند توضیحش دهد.
هر دو نشانی سرور را سنجاق کنید
سرور درونی پیش از اتصال کلاینت درونی به یک مسیر میزبان از طریق دستگاه بیرونی نیاز دارد. همان ورودی است که انتقال درونی را داخل تونل بیرونی نگه میدارد، بهجای آنکه بگذارد از مسیر پیشفرض پیروی کند.
ip route add 203.0.113.9/32 dev tun0
ip route get 203.0.113.9یک مسیر میزبان متناظر برای سرور بیرونی هم به جدول اصلی اضافه کنید، وگرنه اتصال دوباره میتواند انتقال خود کلاینت بیرونی را به تونل خودش بفرستد. ترتیب اختیاری نیست: این ورودیها را پس از وجود رابط مربوطه اضافه کنید، یعنی با یک hook یا اسکریپتی که پس از اتصال اجرا میشود. مسیری که به دستگاهی خاموش اشاره کند حذف یا نادیده گرفته میشود، پس ورودیای که هنگام بوت نوشته شده از ترتیبی که فرض کردهاید جان سالم به در نمیبرد.
جستوجو را بررسی کنید نه خواندن کل جدول. خروجی جستوجوی مسیر دستگاه و نشانی مبدأیی را که هسته استفاده خواهد کرد نام میبرد، که دقیقاً همان چیزی است که کلاینت هنگام باز کردن سوکتش میگیرد.
زنجیره را بیازمایید و شکست را دیدنی کنید
هنگام اتصال کلاینت درونی روی رابط بیرونی ضبط کنید. دستدادن درونی باید بهصورت بار رمزشده درون تونل بیرونی دیده شود؛ اگر بهصورت UDP ساده به 203.0.113.9 دیده شود، مسیر میزبان نبوده و زنجیره هرگز ساخته نشده است.
زنجیرهای که بیصدا به لینک فیزیکی بازمیگردد بدترین نتیجه است، چون وضعیت رابط چیزی اعلام نمیکند و ترافیک درونی از نشانی مبدأ اشتباه بیرون میرود. اگر کلاینت دوم بتواند وقتی تونل بیرونی میافتد مستقیماً دوباره وصل شود، آن مسیر را غیرفعال کنید یا آن را به رابط بیرونی ببندید تا شکست دیده شود.
با دو جریان و یک خروجی قواعد تمام کنید: اتصال علامتخورده و بدون علامت باید مسیرهای متفاوتی بگیرند و ip rule show باید اولویتی را که در نظر داشتید فهرست کند. اگر هر دو جریان یک مسیر را بگیرند، علامت جایی که فکر میکنید اعمال نمیشود.