گیت‌وی‌های پرظرفیت VPN که هم‌زمان به ده‌ها هزار تونل رمزنگاری‌شده سرویس می‌دهند، معمولاً با یک گلوگاه سخت‌افزاری پنهان مواجه می‌شوند: عدم تعادل شدید بار کاری بین هسته‌های پردازنده. یک سرور فیزیکی مدرن با ۳۲ یا ۶۴ هسته پردازشی ممکن است میانگین مصرف پردازنده زیر ۲۵ درصد را ثبت کند، اما در ساعات اوج مصرف بسته‌های ورودی را از دست بدهد. با بررسی معیارهای سیستم مشخص می‌شود که هسته پردازنده شماره ۰ به میزان ۱۰۰ درصد درگیر وقفه‌های نرم‌افزاری (ksoftirqd/0 و NET_RX_SOFTIRQ) شده است، در حالی که سایر هسته‌ها کاملاً بیکار مانده‌اند.

این عدم تعادل پردازشی ناشی از نحوه تعامل کارت‌های شبکه و پروتکل‌های کپسوله‌سازی تونل با هسته سیستم‌عامل است. هنگامی که فناوری سخت‌افزاری Receive Side Scaling (RSS) قادر نیست ترافیک کپسوله‌شده را به‌طور یکنواخت توزیع کند، هسته لینوکس دو سازوکار نرم‌افزاری کارآمد ارائه می‌دهد: هدایت بسته‌های دریافتی (RPS) و هدایت جریان‌های دریافتی (RFS). پیکربندی اصولی این دو سازوکار به مدیران شبکه اجازه می‌دهد پردازش بسته‌ها را در میان تمامی هسته‌های در دسترس توزیع کنند و هم‌زمان کارایی حافظه کش پردازنده را حفظ نمایند.

گلوگاه کپسوله‌سازی تونل در دریافت ترافیک

کارت‌های شبکه فیزیکی (NIC) با استفاده از فناوری سخت‌افزاری RSS فریم‌های ورودی را میان چندین صف سخت‌افزاری توزیع می‌کنند. کارت شبکه با محاسبه هش توپلیتز (Toeplitz) بر روی سرآیند بسته — معمولاً ترکیب چهارتایی شامل نشانی IP مبدا، IP مقصد، پورت مبدا و پورت مقصد — هر جریان را به یک بافر حلقوی دریافت (RX ring buffer) می‌فرستد. هر صف سخت‌افزاری یک وقفه MSI-X اختصاصی ایجاد می‌کند که به یک هسته پردازنده معین متصل است.

محدودیت سخت‌افزاری RSS در مواجهه با ترافیک کپسوله‌شده:
[ کلاینت‌های راه دور ] ---> [ بسته‌های UDP بیرونی پروتکل‌های WireGuard / IPsec / RiceGuard ]
                                        |
                                        v
                       [ پورت مقصد یکسان روی سرور (مانند UDP 51820) ]
                                        |
                          [ محاسبه هش در تراشه کارت شبکه ]
                                        |
       =================================================
       تمامی جریان‌ها به صف دریافت RX 0 هدایت می‌شوند (یک وقفه)
       =================================================
                                        |
                                        v
       [ هسته شماره 0: 100% ksoftirqd / NET_RX_SOFTIRQ ] ---> سرریز بافر دریافت!
       [ هسته‌های 1 تا 31: بار وقفه‌های نرم‌افزاری صفر درصد ]

هنگامی که هزاران کاربر با پروتکل‌های WireGuard یا IPsec به یک متمرکزکننده شبکه متصل می‌شوند، ترافیک لایه بیرونی به یک پورت شنود مشترک ارسال می‌شود. اگر بسته‌های کلاینت‌ها از گیت‌وی‌های NAT ارائه‌دهنده اینترنت (CGNAT) عبور کنند، مقادیر هش چهارتایی یکسان می‌شوند و بخش عمده ترافیک روی تعداد انگشت‌شماری از صف‌های کارت شبکه انباشته خواهد شد.

افزون بر این، رابط‌های مجازی تونل (tun یا tap) فاقد توزیع سخت‌افزاری چندصفی هستند. پس از آن‌که بسته رمزگشایی شد و داده‌های داخلی به پشته شبکه تحویل داده شدند، پردازش وقفه‌های نرم‌افزاری روی همان هسته‌ای متمرکز می‌شود که عملیات رمزگشایی را انجام داده است. با پر شدن بافرهای حلقوی، نوسان تاخیر افزایش می‌یابد و افت خاموش بسته‌ها کیفیت ارتباطات صوتی و داده‌ای را تحت تاثیر قرار می‌دهد.

سازوکار Receive Packet Steering: توزیع نرم‌افزاری چند‌هسته‌ای

سازوکار هدایت بسته‌های دریافتی (RPS) پس از اتمام وقفه سخت‌افزاری اولیه، وظیفه پخش بار پردازش بسته را روی ماسکی از هسته‌های پردازنده که توسط مدیر سیستم مشخص شده است بر عهده می‌گیرد. بر خلاف RSS که مستقیماً روی تراشه کارت شبکه پیاده‌سازی شده، RPS در لایه نرم‌افزاری و در جریان پیمایش NAPI درایور شبکه اجرا می‌شود (netif_receive_skb).

معماری توزیع نرم‌افزاری بسته‌ها با RPS:
[ ورود بسته به صف سخت‌افزاری RX 0 ]
                 |
                 v
       [ روتین پیمایش NAPI ]
                 |
  [ ماسک پردازنده rps_cpus: برای مثال 0x0000ffff (هسته‌های 0 تا 15) ]
                 |
    [ محاسبه هش jhash بر روی سرآیندهای بسته ]
                 |
                 v
   [ قرار دادن sk_buff در صف input_pkt_queue هسته مقصد ]
                 |
   [ وقفه بین‌پردازنده‌ای (IPI): فعال‌سازی NET_RX روی هسته مقصد ]
                 |
     +-----------+-----------+-----------+
     |           |           |           |
     v           v           v           v
  [هسته 0]    [هسته 1]    [هسته 2]    [هسته 3] ... [هسته 15]

با ورود بسته، درایور با بررسی سیستم فایل sysfs مطمئن می‌شود که آیا قابلیت RPS فعال است یا خیر. در صورت فعال بودن، هسته با الگوریتم جنکینز (jhash) سرآیند بسته را هش می‌کند. نتیجه این هش به یکی از هسته‌های مشخص‌شده در ماسک بیتی rps_cpus متناظر می‌گردد.

هسته پردازنده دریافت‌کننده، توصیف‌گر sk_buff را در صف ورودی هسته مقصد (input_pkt_queue) قرار می‌دهد و یک وقفه بین‌پردازنده‌ای (IPI) صادر می‌کند. هسته مقصد بلافاصله بقیه مراحل را مدیریت می‌کند: اجرای قواعد فایروال، مسیریابی و تحویل بسته به سوکت کاربری.

سازوکار Receive Flow Steering: تطبیق جریان با حافظه کش پردازنده

با وجود این‌که RPS بار کاری را بین هسته‌ها توزیع می‌کند، انتخاب هسته مقصد صرفاً بر مبنای هش ریاضی بسته است و اطلاعی از محل استقرار ترد برنامه‌ای که قصد خواندن داده از سوکت را دارد ندارد.

اگر هسته ۴ بسته را در پشته شبکه هسته پردازش کند، اما پردازشگر کاربری نرم‌افزار روی هسته ۱۲ منتظر خواندن داده باشد، بایت‌های بسته باید از مسیر گذرگاه‌های حافظه میان پردازنده‌ها جابه‌جا شوند. این مهاجرت بین‌هسته‌ای موجب هدررفت خطوط کش L1/L2 پردازنده، بی‌اعتبار شدن حافظه موقت و کاهش پهنای باند حافظه اصلی می‌شود.

سازوکار هدایت جریان دریافتی (RFS) این مشکل را برطرف می‌کند: این سازوکار پردازش بسته را مستقیماً به همان هسته‌ای هدایت می‌کند که برنامه کاربری در حال حاضر روی آن سوکت در حال فراخوانی داده است.

شیوه تطبیق جریان در RFS:
1. ترد برنامه روی هسته 3 دستور recvmsg(sock_fd) را صدا می‌زند
   ---> هسته جدول جریان عمومی (rps_sock_flow_table) را به‌روز می‌کند:
        [ هش جریان #A18C -> در حال اجرا روی هسته 3 ]

2. بسته بعدی جریان #A18C وارد کارت شبکه می‌شود
   ---> درایور جدول rps_sock_flow_table را بررسی می‌کند:
        هسته هدف = هسته 3 (داده‌ها پیشاپیش در کش موجود است!)

3. شیء sk_buff مستقیم وارد input_pkt_queue هسته 3 می‌شود
   ---> وقفه‌های نرم‌افزاری و برنامه از یک کش مشترک L1/L2 بهره می‌برند!

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

  1. جدول عمومی جریان سوکت‌ها (rps_sock_flow_table): با فراخوانی توابع سیستمی نظیر recvmsg(2) یا read(2) به‌طور خودکار به‌روزرسانی می‌شود. در این جدول هش جریان و شماره هسته پردازنده فراخواننده ثبت می‌گردد.
  2. جدول محلی هر صف (rps_flow_table): به شکل مستقل در هر صف دریافت کارت شبکه نگهداری می‌شود و شماره آخرین هسته‌ای را که بسته‌ای از این جریان را پردازش کرده در خود ذخیره می‌کند.

پیش از ارسال بسته به هسته مشخص‌شده، سازوکار RFS جدول محلی صف را بررسی می‌کند. اگر برنامه به تازگی به هسته دیگری منتقل شده باشد، RFS تا زمان تخلیه کامل بسته‌های انباشته‌شده در هسته قبلی صبر می‌کند و سپس فرآیند هدایت را تغییر می‌دهد. این طراحی مانع از برهم خوردن ترتیب دریافت بسته‌ها می‌شود.

پیکربندی عملی: پیاده‌سازی RPS و RFS روی سرور VPN

مراحل زیر شیوه اعمال این تنظیمات را روی یک سرور ۱۶ هسته‌ای با کارت شبکه فیزیکی eth0 و رابط تونل WireGuard با نام wg0 تشریح می‌کند.

گام اول: تخصیص ماسک هسته‌ها برای RPS

در یک سرور ۱۶ هسته‌ای (از 0 تا 15)، ماسک هگزادسیمال 0xffff تمام هسته‌ها را فعال می‌کند. برای پایداری بیشتر، می‌توان هسته‌های 0 و 1 را به امور مدیریتی و وقفه‌های سخت‌افزاری اختصاص داد و هسته‌های 2 تا 15 (ماسک 0xfffc) را برای پردازش شبکه تنظیم نمود:

# فعال‌سازی RPS برای هسته‌های 2 تا 15 در تمامی صف‌های eth0
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo "fffc" > "$rx_queue/rps_cpus"
done

# فعال‌سازی RPS روی اینترفیس مجازی تونل
echo "fffc" > /sys/class/net/wg0/queues/rx-0/rps_cpus

گام دوم: تنظیم اندازه جداول RFS عمومی و محلی

ظرفیت جداول باید متناسب با تعداد اتصال‌های هم‌زمان باشد. برای سروری با ۳۲۷۶۸ اتصال فعال، مقدار توان بعدی عدد ۲ یعنی ۶۵۵۳۶ انتخاب می‌شود:

# تنظیم ظرفیت جدول عمومی جریان سوکت‌ها
sysctl -w net.core.rps_sock_flow_entries=65536

# تقسیم متوازن ظرفیت بین صف‌های کارت شبکه (۴ صف: 65536 تقسیم بر 4 = 16384)
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo 16384 > "$rx_queue/rps_flow_cnt"
done

گام سوم: افزایش ظرفیت صف بافر ورودی هسته

در ترافیک‌های ناگهانی، در صورتی که سرعت پردازش وقفه‌های نرم‌افزاری کمتر از سرعت ورود بسته‌ها باشد، صف هسته هدف ممکن است سرریز کند. بالا بردن مقدار netdev_max_backlog مانع از افت بسته‌ها در گزارش /proc/net/softnet_stat می‌شود:

# افزایش سقف صف انتظار هر هسته از 1000 به 10000 بسته
sysctl -w net.core.netdev_max_backlog=10000

# افزایش سهمیه پردازش بسته‌ها در هر چرخه NAPI
sysctl -w net.core.netdev_budget=600
sysctl -w net.core.netdev_budget_usecs=4000

یکپارچه‌سازی معماری در پروتکل RiceGuard VPN

پروتکل مسیریابی لایه سه RiceGuard که توسط تیم VPNRice طراحی شده است، تعامل نزدیکی با ساختار هدایت بسته در هسته لینوکس دارد:

  1. سازگاری با معماری حافظه NUMA: پروتکل RiceGuard حلقه‌های ورودی/خروجی و بخش‌های رمزنگاری را در نودهای اختصاصی NUMA تفکیک می‌کند. برای بسته‌هایی که از کارت شبکه متصل به نود 0 وارد می‌شوند، ماسک rps_cpus منحصراً روی هسته‌هایی تنظیم می‌شود که کش L3 مشترک با آن گذرگاه دارند.
  2. همگام‌سازی بلادرنگ جداول جریان: در زمان افزایش اتصال‌ها، پردازشگرهای RiceGuard نشانگرهای لازم را در جداول هسته ثبت می‌کنند تا وقفه‌های نرم‌افزاری و پردازش‌های برنامه همواره در یک گستره کش باقی بمانند.
  3. داده‌های حاصل از آزمایش: در آزمایش‌های بار ترافیکی ۱۰ گیگابیت بر ثانیه با ۲۵ هزار اتصال کلاینت، هماهنگی RPS و RFS تاخیر صدک ۹۹ (p99) را از ۸.۴ میلی‌ثانیه به ۱.۹ میلی‌ثانیه رساند و افت ناشی از قفل شدن هسته‌ها را به صفر رساند.

پرسش‌های متداول

تفاوت اساسی میان فناوری‌های RSS و RPS و RFS چیست؟

فناوری RSS قابلیتی سخت‌افزاری در تراشه کارت شبکه است که از صف‌های فیزیکی و وقفه‌های MSI-X بهره می‌برد. سیستم RPS راهکاری نرم‌افزاری در هسته سیستم‌عامل است که وظیفه توزیع بار را پس از اتمام وقفه سخت‌افزاری انجام می‌دهد. فناوری RFS نسخه بهینه‌تر RPS است که بسته‌ها را مشخصاً به همان هسته‌ای می‌فرستد که برنامه مرتبط با سوکت روی آن فعال است.

آیا فعال‌سازی RPS موجب افزایش مصرف پردازنده می‌شود؟

سازوکار RPS سربار بسیار اندکی در حد ۱ تا ۲ درصد پردازنده به علت محاسبه هش و ارسال وقفه‌های IPI دارد، اما به دلیل رفع قفل تک‌هسته‌ای و توزیع همگن بار، توان عملیاتی نهایی سرور را به میزان چشمگیری بالا می‌برد.

آیا امکان به‌کارگیری RPS و RFS برای کارت‌های مجازی tun/tap وجود دارد؟

بله، اینترفیس‌های مجازی tun و tap بهره فراوانی از RPS می‌برند. از آن‌جا که کارت‌های مجازی معمولاً تنها یک صف انتقال دارند، فعال کردن rps_cpus روی rx-0 پردازش بسته‌های رمزگشایی‌شده را بین هسته‌های خالی پخش کرده و گلوگاه تونل را برطرف می‌سازد.