سناریو را از همان دفتر کاری شروع میکنیم که همهٔ ما میشناسیم: یک لینک اینترنت که بدترین ساعت ممکن قطع میشود، یک ISP دوم که ارزانتر است ولی در ساعات شلوغی نفس کم میآورد، و دهها کاربر که ترافیکشان از یک روتر MikroTik میگذرد. در این نوشته قدمبهقدم پیکربندی میکنیم تا ترافیک بین دو ISP تقسیم شود، با قطع یکی از لینکها شبکه نفس نکشد و فایروال روتر هم در حد یک روتر لبهٔ واقعی سختگیرانه باشد. همهٔ کدها برای RouterOS 7 نوشته شدهاند و تفاوتهای نسخهٔ ۶ را هم جداگانه میبینید.
سناریو و نقشهٔ شبکه
سه رابط در بازی است؛ اسمها و آدرسها را از همینجا ثابت میکنیم تا کدهای مراحل بعد دقیقاً همین سناریو را پوشش دهند:
- ether1 — لینک ISP اول (پهنایباند اصلی):
192.0.2.2/30با گیتوی192.0.2.1 - ether2 — لینک ISP دوم (پشتیبان یا ارزانتر):
198.51.100.2/30با گیتوی198.51.100.1 - bridge-lan — شبکهٔ داخلی (پورتهای ۳ به بعد و بیسیم):
10.10.10.1/24
.backup، خروجی export را میتوانید در گیت نگه دارید، diff بگیرید و خطبهخط بازبینی کنید — این همان چیزی است که یک بازگشت اضطراری را به یک کار دو دقیقهای تبدیل میکند:/system backup save name=before-lb
/export file=before-lb
گام ۱ — بالا آوردن دو WAN
اول آدرسها و بعد NAT. نکتهٔ مهم این است که برای هر WAN یک قاعدهٔ masquerade جداگانه مینویسیم؛ یک قاعدهٔ سراسری وقتی ترافیک بین دو مسیر تقسیم میشود، بستههایی را که از WAN دیگر خارج میشوند بدون ترجمهٔ درست رها میکند و سایتهای مقصد بستههای «بیمنبع» میبینند:
/ip address
add address=192.0.2.2/30 interface=ether1 comment="WAN1 - main ISP"
add address=198.51.100.2/30 interface=ether2 comment="WAN2 - backup ISP"
/ip firewall nat
add chain=srcnat out-interface=ether1 action=masquerade comment="NAT out WAN1"
add chain=srcnat out-interface=ether2 action=masquerade comment="NAT out WAN2"
/ip dns
set servers=1.1.1.1,8.8.8.8 allow-remote-requests=yes
add-default-route=no و use-peer-dns=no بگذارید. مسیر پیشفرضی که DHCP کلاینت تزریق میکند فقط به جدول main میرود و هرگز نمیتواند ترافیک mark شده را ببرد؛ همان یک قاعدهٔ خودکار کافی است تا کل لود بالانسینگ شما بیصدا بیاثر شود.گام ۲ — مسیرهای مستقل و failover خودکار
در RouterOS 7 برای هر ISP یک جدول مسیریابی جدا میسازیم؛ این جدولها فقط توسط routing-mark استفاده میشوند. بعد در جدول main هم دو مسیر پیشفرض با distance متفاوت میگذاریم تا اگر ISP اول مرد، کل شبکه خودکار روی ISP دوم بیفتد — بدون هیچ اسکریپتی:
/routing table
add name=via-isp1 fib
add name=via-isp2 fib
/ip route
# dedicated tables used by routing marks
add dst-address=0.0.0.0/0 gateway=192.0.2.1 routing-table=via-isp1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 routing-table=via-isp2 check-gateway=ping
# main table: ISP1 primary, ISP2 automatic failover
add dst-address=0.0.0.0/0 gateway=192.0.2.1 distance=1 check-gateway=ping
add dst-address=0.0.0.0/0 gateway=198.51.100.1 distance=2 check-gateway=ping
check-gateway=ping هر ده ثانیه گیتوی را ping میکند و بعد از دو شکست، آن مسیر را از جدول حذف میکند؛ یعنی قطعی را حداکثر در حدود بیست ثانیه میفهمید. یک قاعدهٔ امنیتی هم اینجا کار میکند که خیلیها نمیدانند: اگر ترافیکی با routing-mark به جدولی فرستاده شود که مسیر فعالی ندارد، RouterOS بهطور خودکار به جدول main برمیگردد. پس وقتی ISP1 از دسترس خارج شود، ترافیک mark شدهٔ «به-ISP1» هم بدون هیچ تغییری از ISP2 میگذرد.
1.1.1.1 probe بزنید تا «مودم روشن ولی اینترنت قطع» هم تشخیص داده شود؛ در RouterOS 7 یعنی gateway مسیر اصلی را همان IP عمومی بگذارید و گیتوی واقعی را با scope=10 target-scope=10 بهعنوان مسیر host تعریف کنید.گام ۳ — منطق Mangle در سه لایه
قلب کار همینجاست. سه قانون طلایی را به ترتیب بنویسید و جای هیچکدام را عوض نکنید:
- هر اتصالی که از بیرون وارد یکی از WANها شده، پاسخش هم از همان WAN بیرون برود. وگرنه بانکها، ریموتدسکتاپ و هر سرویس session محوری از هم میپاشد.
- فقط اتصالهای جدید شبکهٔ داخلی با PCC تقسیم شوند — قید
connection-mark=no-markدر اینجا یک قید تزئینی نیست؛ تضمین میکند اتصالهای از قبل mark شده دوباره طبقهبندی نشوند. - routing-mark از connection-mark مشتق شود، نه مستقیم از PCC. به این ترتیب تصمیم فقط یک بار برای هر اتصال گرفته میشود و تمام بستههای آن اتصال از یک مسیر میروند.
/ip firewall mangle
# 1) inbound WAN connections keep their own path
add chain=prerouting in-interface=ether1 connection-mark=no-mark action=mark-connection new-connection-mark=to-isp1 passthrough=yes
add chain=prerouting in-interface=ether2 connection-mark=no-mark action=mark-connection new-connection-mark=to-isp2 passthrough=yes
# 2) classify every NEW connection from the LAN with PCC (50/50)
add chain=prerouting in-interface=bridge-lan connection-mark=no-mark per-connection-classifier=both-addresses:2/0 action=mark-connection new-connection-mark=to-isp1 passthrough=yes
add chain=prerouting in-interface=bridge-lan connection-mark=no-mark per-connection-classifier=both-addresses:2/1 action=mark-connection new-connection-mark=to-isp2 passthrough=yes
# 3) translate the connection mark into a routing mark
add chain=prerouting connection-mark=to-isp1 action=mark-routing new-routing-mark=via-isp1 passthrough=no
add chain=prerouting connection-mark=to-isp2 action=mark-routing new-routing-mark=via-isp2 passthrough=no
قواعد فایروال در RouterOS از بالا به پایین خوانده میشوند و اولین تطبیق برنده است؛ پس ترتیب همین بلوک را دقیقاً حفظ کنید. passthrough=no در لایهٔ سوم یعنی «کار تمام شد، بسته را رها کن» و از خوردن دو بار کانتر جلوگیری میکند.
گام ۴ — PCC در عمق: کلاسبندی و نسبت دلخواه
per-connection-classifier سه پارامتر دارد: هش چه چیزی باشد، چند bucket بسازد و بسته به کدام bucket بخورد. حالتهای پرکاربرد:
both-addresses— هش روی IP مبدأ و مقصد؛ بهترین انتخاب پیشفرض. یک کاربر همیشه روی یک bucket میافتد و سرعت هر دانلودِ تکی همان یک لینک میماند.both-addresses-and-ports— دانهدرشتتر؛ تعداد اتصال هم در هش میآید و توزیع یکنواختتر میشود، ولی دو اتصال از یک مرورگر میتوانند از دو ISP بروند که برای اکثر سایتها مشکلی ندارد.src-address— کل کاربر روی یک لینک؛ وقتی میخواهید مثلاً اتاق سرور همیشه از ISP2 خارج شود.
عدد بعد از دونقطه تعداد bucketها است و عدد بعد از ممیز، شمارهٔ bucket. چون هش بهطور یکنواخت پخش میشود، میتوانید نسبت غیرمساوی هم بسازید — ترفندی که تقریباً هیچجا مستند نمیشود: بهجای دو bucket ده bucket بسازید و هفتتای آنها را به ISP پرسرعت بدهید:
# ~70/30 split: buckets 0..6 go to ISP1, buckets 7..9 to ISP2
add chain=prerouting in-interface=bridge-lan connection-mark=no-mark per-connection-classifier=both-addresses:10/0 action=mark-connection new-connection-mark=to-isp1 passthrough=yes
add chain=prerouting in-interface=bridge-lan connection-mark=no-mark per-connection-classifier=both-addresses:10/1 action=mark-connection new-connection-mark=to-isp1 passthrough=yes
# ... repeat the same rule for buckets 10/2 up to 10/6 (to-isp1)
add chain=prerouting in-interface=bridge-lan connection-mark=no-mark per-connection-classifier=both-addresses:10/7 action=mark-connection new-connection-mark=to-isp2 passthrough=yes
# ... and for buckets 10/8 and 10/9 (to-isp2)
گام ۵ — Nth: تقسیم بستهمحور برای UDP
PCC در سطح اتصال تصمیم میگیرد؛ Nth در سطح بسته. nth=2,1 یعنی «از هر دو بسته، دومی» را تطبیق بده. برای TCP هیچوقت مناسب نیست (بستههای یک اتصال از دو مسیر مختلف = فاجعه)، ولی برای ترافیکی که state ندارد — مثل فوروارد دوبارهٔ کوئریهای DNS به دو رِزالور، یا رلههای UDP — راهحل تمیزی است:
/ip firewall mangle
add chain=prerouting protocol=udp dst-port=53 dst-address=10.10.10.1 in-interface=bridge-lan nth=2,1 action=mark-connection new-connection-mark=to-isp2 passthrough=yes
add chain=prerouting protocol=udp dst-port=53 dst-address=10.10.10.1 in-interface=bridge-lan connection-mark=no-mark action=mark-connection new-connection-mark=to-isp1 passthrough=yes
قاعدهٔ دوم عمداً بعد از Nth میآید: هر کوئریای که bucket دوم را نخورد، به ISP1 میرود. اگر این الگو را حفظ کنید، هرگز نیازی به شمارش معکوس کانترهای Nth ندارید.
گام ۶ — فایروال: از «باز بودن همه» تا روتر لبهٔ واقعی
با وجود لود بالانسینگ، سطح حملهٔ شما دو برابر شده است: دو مسیر ورودی به شبکه. زنجیرهٔ input را سخت ببندید و در forward فقط ترافیک state دار و داخلی را بپذیرید:
/ip firewall filter
# --- input: protect the router itself
add chain=input connection-state=established,related action=accept
add chain=input connection-state=invalid action=drop
add chain=input in-interface=bridge-lan action=accept
add chain=input in-interface=ether1 protocol=icmp action=accept
add chain=input in-interface=ether2 protocol=icmp action=accept
add chain=input action=drop comment="drop everything else from WAN"
# --- forward: police what crosses the router
add chain=forward connection-state=established,related,untracked action=accept
add chain=forward connection-state=invalid action=drop
add chain=forward in-interface=bridge-lan action=accept
add chain=forward in-interface=ether1,ether2 connection-nat-state=!dstnat action=drop
قاعدهٔ آخر ستون فقرات امنیت است: از بیرون فقط ترافیکی اجازهٔ عبور دارد که شما عمداً برای آن dstnat (port forwarding) نوشتهاید؛ هر چیز دیگری drop میشود. اگر لازم است از بیرون Winbox بزنید، یک address-list از IPهای دفتر بسازید و یک قاعدهٔ accept محدود به همان لیست، قبل از قاعدهٔ drop نهایی بگذارید — و هرگز پورت Winbox را به کل اینترنت باز نگذارید.
گام ۷ — تست، اندازهگیری و تیونینگ
لود بالانسینگ بدون اندازهگیری فقط یک حدس است. این چهار دستور عادت روزمرهٔ شما باید شوند:
/ip route print where routing-table=via-isp1
/ip firewall connection print count-only where connection-mark=to-isp1
/ip firewall connection print count-only where connection-mark=to-isp2
/tool torch ether1 port=any
- تست failover: کابل WAN1 را بکشید؛ حدود بیست ثانیه بعد (دو بار ping ناموفق) مسیر از جدول حذف میشود و ترافیک بهصورت شفاف از ISP2 میگذرد. دست بکشید و برگردد، همانقدر طول میکشد.
- تست توزیع: کانتر قواعد PCC را ده دقیقه نگاه کنید. اختلاف بیش از ۵–۱۰ درصد طبیعی است (ترافیک شما متقارن نیست)، ولی اگر همهچیز روی یک bucket است، احتمالاً همهٔ کاربران پشت یک NAT مشترکاند — به
both-addresses-and-portsسوئیچ کنید. - تیونینگ NAT سنگین: در دفاتر شلوغ جدول connection tracking زود پر میشود؛
tcp-established-timeoutرا از ۲۴ ساعت به ۱–۲ ساعت کم کنید و در Raw جدول قاعدهٔ drop برای SYN flood از WAN اضافه کنید.
اشکالیابی و نکتههای نسخهها
- برخی سایتها یا اپ بانکی باز نمیشود — اتصالهای قبلیاش روی bucket دیگری گیر کرده یا IP خروجی را عوض کرده. همان address-list استثنا را بسازید و به یک ISP ببندید؛ بعد از اعمال، اتصالهای قدیمی را با
/ip firewall connection remove [find]پاک کنید. - بعد از قطعی ISP، همهچیز چند دقیقه قفل است — جدول connection tracking پر از اتصالهای مرده است. یا صبر کنید تا timeout بخورند یا همان دستور remove را بزنید.
- port forwarding جواب میدهد ولی پاسخ به مشتری نمیرسد — لایهٔ اول Mangle را ننوشتهاید؛ پاسخ دارد از WAN دیگر برمیگردد و سرویس گیرنده آن را نمیشناسد.
- سرعت دانلودِ تکی جمع نشد — طبیعی است؛ تقسیم در سطح اتصال است نه بایت. برای جمعشدن سرعت یک فایل باید downloader چنداتصاله استفاده کنید.
- در نسخهٔ ۶ جدول مسیریابی جداگانه وجود ندارد؛ بهجای
routing-table=via-isp1روی خود مسیرrouting-mark=via-isp1مینویسید. check-gateway=pingدر هر دو نسخه یکسان است و منطق failover فرقی نمیکند.- در نسخهٔ ۶ قواعد mangle همیناند؛ فقط نام جداول را به routing-mark مسیرها گره بزنید.
- قبل از ارتقا از ۶ به ۷ همیشه export بگیرید — چند دستور network-related تغییر نام دادهاند و اسکریپتهای خام ممکن است بشکنند.
- از دو کلاینت مختلف سایت تشخیص IP را باز کنید؛ باید هر دو IP عمومی را ببینید.
- کابل هر WAN را جدا بکشید و برگردانید؛ قطعی در ~۲۰ ثانیه باید بدون قطع شدن اتصالهای جدید جبران شود.
- یک port forwarding واقعی را از بیرون (موبایل با دیتا) تست کنید، نه از داخل شبکه.
- کانتر قواعد PCC را ببینید؛ نسبت تقریباً مطابق bucketهاست؟
/exportنهایی را در گیت کامیت کنید؛ پیکربندیِ بینسخهٔ کنترل، پیکربندیِ گمشدهٔ فرداست.
سؤالات متداول
dst-address-list بالاتر از قواعد PCC اضافه کنید. همان الگوی استثنای سایتهای بانکی است؛ ترتیب قواعد، همهچیز است.جمعبندی
چارچوبی که ساختیم سه لایه دارد: مسیرهای مستقل با check-gateway برای failover بیسکریپت، mangle سهلایه برای تقسیم در سطح اتصال، و فایروال زنجیرهای برای سطح حملهٔ دوتایی. اگر همین ترتیب را حفظ کنید، افزودن ISP سوم یعنی فقط تکرار همین الگو با یک bucket دیگر. اگر با OpenWrt کار میکنید، منطق mwan3 دقیقاً همین است؛ تجربهٔ من با OpenWrt را در فریمور سفارشی Google Wifi (Gale) و درسهای واقعی میدانی را در درسهای شبکه از یک سایت صنعتی خواندهاید.