پروتکل‌های نوین تونل‌سازی رمزنگاری‌شده نظیر WireGuard، RiceGuard، IPsec بر بستر UDP و QUIC همگی از بسته داده‌های UDP به عنوان قالب کپسوله‌سازی بیرونی بهره می‌برند. با وجود مزایای برجسته UDP در مدیریت جریان بدون نیاز به ذخیره وضعیت‌های پیچیده و مسیریابی پویا در شبکه‌های گسترده، اجرای تونل‌های ارتباطی بر بستر UDP سنتی هنگام رسیدن به سرعت‌های چند گیگابیت بر ثانیه با چالشی سخت روبه‌رو می‌شود: بار پردازشی به ازای هر بسته مستقل (per-packet overhead).

در معماری کلاسیک شبکه در هسته لینوکس، هر بسته ۱۴۲۰ یا ۱۵۰۰ بایتی که از واسط مجازی TUN/TAP یا سوکت عبور می‌کند، نیازمند تخصیص یک ساختار بافر اختصاصی (struct sk_buff)، بررسی دیواره آتش، تصمیم‌گیری در جدول مسیریابی و وقفه سخت‌افزاری ارسال است. برای پر کردن ظرفیت یک پیوند ۱۰ گیگابیت بر ثانیه، سرور باید بیش از ۸۲۰ هزار بسته مجزا در هر ثانیه پردازش کند. با رسیدن سرعت به ۲۵ یا ۴۰ گیگابیت بر ثانیه، شمار بسته‌ها به بیش از ۳ میلیون در ثانیه (Mpps) افزایش می‌یابد؛ پدیده‌ای که توان هسته‌های پردازنده را به جای الگوریتم‌های رمزنگاری، صرف رسیدگی به امور دفتری سیستم‌عامل می‌کند.

برای رفع این مانع عملکردی، هسته لینوکس مکانیزم‌های بخش‌بندی سراسری UDP موسوم به UDP_SEGMENT یا UDP GSO و تجمیع دریافت سراسری UDP یا UDP_GRO را پیاده‌سازی نموده است. این زیرسیستم‌ها با همکاری یکدیگر، ده‌ها بسته داده خرد را در قالب یک «ابر‌بسته» یکپارچه تا حجم ۶۴ کیلوبایت متصل می‌سازند تا درگاه‌های پرظرفیت شبکه بتوانند ترافیک خود را روی سخت‌افزارهای امروزی توسعه دهند.

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

برای درک اتلاف منابع در مدل پیشین، فرآیند ارسال بسته در یک درگاه شبکه فاقد شتاب‌دهنده UDP را بررسی می‌کنیم:

[ارسال سنتی UDP: ۴۵ بار پیمایش مجزا در پشته هسته]
برنامه در فضای کاربر ──> رمزنگاری ۴۵ بسته (هر یک ۱۴۲۰ بایت)
                             │
                             ▼
            ۴۵ فراخوانی sendmsg() یا ۱ بار sendmmsg(45)
                             │
┌────────────────────────────┴────────────────────────────┐
│ پشته شبکه هسته لینوکس:                                  │
│ • تخصیص ۴۵ ساختار داده مستقل struct sk_buff             │
│ • ۴۵ بار استعلام جداول وضعیت Netfilter و conntrack      │
│ • ۴۵ ارزیابی جدول مسیر FIB                              │
│ • ۴۵ عملیات صف‌بندی در ساختار qdisc                     │
│ • ۴۵ بار به‌روزرسانی اشاره‌گر حلقه کارت شبکه             │
└────────────────────────────┬────────────────────────────┘
                             │
                             ▼
            ۴۵ فریم فیزیکی ارسالی روی کابل شبکه

حتی زمانی که نرم‌افزارها از فراخوانی‌های گروهی مانند sendmmsg(2) بهره می‌برند، هسته موظف است نمونه‌های مجزای sk_buff را در تمامی لایه‌ها بسازد و پیگیری کند. بی‌اعتبار شدن حافظه نهان پردازنده، اعمال اتمیک شمارش ارجاعات حافظه و قفل‌های ساختار صف qdisc تا ۷۰ درصد کل زمان پردازنده را در لبه شبکه می‌بلعند.

دستورالعمل‌های برداری جدید مانند AVX-512 و ARM Neon فرآیند رمزنگاری الگوریتم‌های ChaCha20-Poly1305 یا AES-256-GCM را با سرعت فراتر از ۴۰ گیگابیت بر ثانیه روی هر هسته میسر می‌سازند. گلوگاه بنیادین در محاسبات رمزنگاری نیست، بلکه در ثبت و مدیریت خرد بسته‌ها در لایه سیستم‌عامل است.

سازوکار UDP GSO: ایجاد ابربسته‌های ۶۴ کیلوبایتی در ارسال

قابلیت UDP GSO که در هسته لینوکس ۴٫۱۸ معرفی گردید، به برنامه‌ها امکان می‌دهد یک بلوک حافظه پیوسته تا حجم ۶۵٬۵۳۵ بایت (۶۴ کیلوبایت) را تنها با یک دستور سوکت به هسته بسپارند و شکستن داده به بسته‌های فیزیکی را تا نزدیک‌ترین نقطه به سخت‌افزار به تاخیر بیندازند.

به جای ارسال ۴۵ بسته مجزای ۱۴۲۰ بایتی، درگاه شبکه ۴۵ چارچوب را پشت سر هم در یک بافر می‌چیند و با افزودن پیام کنترلی (cmsg) اندازه هر بخش را با UDP_SEGMENT معین می‌کند:

[مسیر پردازش UDP GSO و تفکیک دیرهنگام فریم‌ها]
درگاه شبکه: ساخت بافر پیوسته ۶۴ کیلوبایتی (حاوی ۴۵ بسته ۱۴۲۰ بایتی)
     │
     ▼ ارسال با sendmsg() و شناسه SOL_UDP / UDP_SEGMENT (اندازه = ۱۴۲۰)
┌─────────────────────────────────────────────────────────────┐
│ هسته لینوکس: انتقال کل ۶۴ کیلوبایت به عنوان یک sk_buff واحد │
│ • ۱ بار ارزیابی قوانین Netfilter                            │
│ • ۱ بار جست‌وجوی جدول مسیر FIB                               │
│ • ۱ بار درج در صف qdisc                                     │
└──────────────────────────────┬──────────────────────────────┘
                               │
            ┌──────────────────┴──────────────────┐
            ▼                                     ▼
   [پشتیبانی سخت‌افزاری USO]              [بخش‌بندی نرم‌افزاری GSO]
   تراشه کارت شبکه بسته‌ها را در لحظه    هسته در آخرین مرحله در تابع
   خروج فیزیکی به ۴۵ فریم می‌شکند       dev_queue_xmit بافر را می‌شکند

از آنجا که بسته در تمام طول پیمایش پشته شبکه لینوکس به عنوان یک ابربسته ۶۴ کیلوبایتی حفظ می‌شود، قوانین امنیتی و محاسبات مسیریابی به جای ۴۵ بار، تنها یک بار اجرا می‌گردند.

در صورتی که کارت شبکه فیزیکی از قابلیت بخش‌بندی سخت‌افزاری UDP یا USO پشتیبانی کند، هسته کل بلوک را به صف سخت‌افزاری تحویل داده و شکستن بسته‌ها را به تراشه ASIC می‌سپارد. در صورت نبود این ویژگی، هسته لینوکس در آخرین گام پیش از تحویل به درایور (dev_queue_xmit) عملیات تقطیع را به اجرا درمی‌آورد.

سازوکار UDP GRO: بازسازی جریان داده با Frag_List

در حالی که GSO مسیر ارسال داده (TX) را دگرگون می‌کند، درگاه‌های ارتباطی در مسیر دریافت (RX) نیز با بار سنگین بسته‌های پرشمار مواجهند. از نگارش ۵٫۰ هسته لینوکس، ویژگی UDP_GRO برای مهار این چالش در سمت دریافت افزوده شد.

هنگامی که بسته‌های فشرده UDP وارد حلقه پردازش NAPI در درایور کارت شبکه می‌شوند، زیرسیستم GRO مشخصات بسته‌ها را ارزیابی می‌کند: تطابق نشانی‌های مبدا و مقصد، درگاه‌های ارتباطی و طول یکسان بار داده.

به جای بیدار کردن برنامه برای ۴۵ بسته پیاپی، زیرسیستم GRO بسته‌های منطبق را درون فهرست اشاره‌گرهای frag_list در اولین ساختار sk_buff به یکدیگر پیوند می‌دهد:

[خط لوله تجمیع بسته‌های ورودی در UDP GRO]
کابل فیزیکی: ورود ۴۵ بسته رمزنگاری‌شده متوالی
                     │
                     ▼
          حلقه پویشگر NAPI در درایور شبکه
                     │
                     ▼ اجرای تابع napi_gro_receive()
┌─────────────────────────────────────────────────────────────┐
│ زیرسیستم GRO: بررسی شناسه ۵ بخشی و طول بسته‌ها             │
│ بسته اول به عنوان sk_buff پیشرو در نظر گرفته می‌شود         │
│ ۴۴ بسته بعدی به skb_shinfo(skb)->frag_list افزوده می‌شوند  │
└─────────────────────────────┬───────────────────────────────┘
                              │
                              ▼ عبور از پشته دریافت به شکل یک sk_buff واحد
                       لایه سوکت UDP
                              │
                              ▼ فراخوانی recvmsg() با پرچم UDP_GRO
                 فرآیند اجرایی درگاه در فضای کاربر:
            دریافت کل حجم ۶۴ کیلوبایت تنها در ۱ فراخوانی

با گردهم‌آوری داده‌ها در سطح درایور، لینوکس تا ۹۵ درصد از تخصیص‌های میانی حافظه جلوگیری به عمل می‌آورد. با تنظیم گزینه UDP_GRO در سوکت، تنها یک دستور recvmsg(2) کل ابربسته ۶۴ کیلوبایتی را به همراه اطلاعات مرزبندی قطعات به نرم‌افزار تحویل می‌دهد.

نمونه پیاده‌سازی کاربردی: تنظیم سوکت‌های UDP GSO و GRO

کد زیر به زبان C شیوه فعال‌سازی دریافت تجمیعی GRO و ارسال ابربسته‌های GSO را نشان می‌دهد:

#include <netinet/in.h>
#include <netinet/udp.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define SEGMENT_SIZE 1420
#define NUM_SEGMENTS 45
#define BUFFER_SIZE  (SEGMENT_SIZE * NUM_SEGMENTS)

/* راه‌اندازی سوکت با قابلیت تجمیع بسته‌های دریافتی GRO */
int setup_vpn_socket(int port)
{
    int fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
    int gro_enable = 1;

    /* فعال‌سازی قابلیت UDP GRO روی سوکت دریافت */
    if (setsockopt(fd, SOL_UDP, UDP_GRO, &gro_enable, sizeof(gro_enable)) < 0) {
        perror("خطا در تنظیم setsockopt برای UDP_GRO");
    }

    struct sockaddr_in sin = {
        .sin_family = AF_INET,
        .sin_port = htons(port),
        .sin_addr.s_addr = INADDR_ANY
    };
    bind(fd, (struct sockaddr *)&sin, sizeof(sin));
    return fd;
}

/* ارسال ابربسته تجمیع‌شده ۶۴ کیلوبایتی با روش UDP GSO */
ssize_t send_gso_superpacket(int fd, const char *payload, size_t total_len, 
                            struct sockaddr_in *dst)
{
    struct msghdr msg = {0};
    struct iovec iov = {
        .iov_base = (void *)payload,
        .iov_len = total_len
    };
    
    char cbuf[CMSG_SPACE(sizeof(uint16_t))];
    memset(cbuf, 0, sizeof(cbuf));

    msg.msg_name = dst;
    msg.msg_namelen = sizeof(*dst);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = cbuf;
    msg.msg_controllen = sizeof(cbuf);

    /* الصاق پیام کنترلی UDP_SEGMENT برای تعیین مرزهای بخش‌بندی */
    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_UDP;
    cmsg->cmsg_type = UDP_SEGMENT;
    cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t));
    
    uint16_t gso_size = SEGMENT_SIZE;
    memcpy(CMSG_DATA(cmsg), &gso_size, sizeof(gso_size));

    return sendmsg(fd, &msg, 0);
}

این چارچوب به هسته شبکه امکان می‌دهد قطعات بزرگ ۶۴ کیلوبایتی را به صورت مستقیم پردازش کرده و بار ناشی از فراخوانی‌های پیاپی سیستم را مهار کند.

ارزیابی و سنجش شاخص‌های کارایی عملیاتی

در آزمایش‌های تحت فشار دوطرفه روی سرورهای Intel Xeon Platinum 8480+ (مجموعاً ۱۱۲ هسته) مجهز به کارت‌های شبکه Mellanox ConnectX-6 Dx 100GbE تحت هسته لینوکس ۶٫۶، استفاده از این روش جهش چشمگیری در شاخص‌ها ایجاد کرد:

شاخص سنجش عملکرد پشته استاندارد UDP (با recvmmsg و sendmmsg) پشته بهینه‌شده با UDP GSO و UDP_GRO ضریب بهبود
توان انتقال هر هسته ۲٫۱۴ گیگابیت بر ثانیه ۱۸٫۷۲ گیگابیت بر ثانیه رشد ۸٫۷ برابری
شمار پیمایش بسته در هسته ۱٬۸۸۰٬۰۰۰ بسته بر ثانیه ۲۱۵٬۰۰۰ ابربسته بر ثانیه کاهش ۸۸٫۵ درصدی
مصرف فرکانس CPU بر گیگابایت ۴٫۸۲ گیگاهرتز-ثانیه ۰٫۵۸ گیگاهرتز-ثانیه افزایش ۸٫۳ برابری بهره‌وری
تواتر وقفه‌های هسته لینوکس ۱۴۲٬۰۰۰ در هر ثانیه ۱۶٬۵۰۰ در هر ثانیه افت ۸۸٫۳ درصدی وقفه‌ها
نوسان تاخیر چارک ۹۹ (Jitter) ۳٫۸۴ میلی‌ثانیه ۰٫۴۲ میلی‌ثانیه پایداری ۹٫۱ برابری تاخیر

انتقال فرآیند قطعه‌بندی به سطح فیزیکی و پیوند فریم‌ها در حلقه NAPI، منابع پردازشی باارزشی را برای محاسبات امنیتی آزادسازی می‌نماید.

کاربرد میدانی در گره‌های پیرامونی سامانه RiceGuard Anycast

سامانه جامع RiceGuard فناوری‌های UDP GSO و GRO را بر بستر سراسری Anycast خود پیاده‌سازی نموده تا پایداری و بازدهی بالا را برای هزاران تونل هم‌روند تامین کند:

  • ارسال داده در نهایت سرعت فیزیکی خط: گره‌های لبه Anycast در RiceGuard توانایی هدایت پایدار ۴۰ گیگابیت بر ثانیه را روی هر سرور با حفظ درصد پایینی از مصرف پردازنده به ثبت رسانده‌اند.
  • تثبیت تاخیر و مهار ناپایداری زمانی: حذف توقف‌های ناشی از صف‌بندی میلیون‌ها بسته مستقل، نوسانات جریان داده را از بین برده و ارتباطی یکنواخت برای برنامه‌های حساس مانند مکالمات صوتی تصویری و سامانه‌های بلادرنگ مهیا می‌سازد.
  • سازگاری هوشمند با سخت‌افزار: معماری RiceGuard قابلیت‌های کارت شبکه را بررسی می‌کند؛ در صورت وجود شتاب‌دهنده‌های USO/GRO مسیر مستقیم سخت‌افزاری فعال می‌شود و روی سرورهای قدیمی‌تر، الگوریتم‌های نرم‌افزاری بهینه‌شده هسته به کار گرفته می‌شوند.

به‌کارگیری UDP GSO و GRO تحولی اساسی در بستر انتقال داده‌های رمزشده پدید می‌آورد و انعطاف‌پذیری نرم‌افزار را به توان عملیاتی سخت‌افزارهای مدرن پیوند می‌زند.