دستیابی به نرخ تبادل چند گیگابیت بر ثانیه در درگاه‌های کاربری VPN مستلزم بازنگری در شیوه انتقال بسته‌های داده میان کارت شبکه فیزیکی (NIC) و فرایندهای اجرایی نرم‌افزار است. در سامانه‌های سنتی، تونل‌های نرم‌افزاری پیاده‌سازی شده با زبان‌های Go، Rust یا C از طریق واسط‌های مجازی TUN/TAP با سیستم‌عامل تعامل دارند. هرچند رابط‌های TUN سازگاری گسترده‌ای ارائه می‌دهند، اما تحمیل بار پردازشی سنگینی به همراه دارند: هر بسته ورودی نیازمند سوئیچ زمینه میان فضای هسته و کاربر، تخصیص ساختار داده struct sk_buff در هسته و کپی داده از حافظه با فراخوانی copy_to_user() است.

در ترافیک متراکم خطوط ۱۰ یا ۴۰ گیگابیت بر ثانیه، این سربار سبب اشباع خطوط انتقال حافظه و تخلیه پیاپی حافظه نهان پردازنده می‌شود و توان پردازشی هر هسته پردازشی را در حدود ۱.۵ میلیون بسته در ثانیه (Mpps) متوقف می‌کند. اگرچه کیت توسعه مسیر داده DPDK با تصاحب مستقیم سخت‌افزار این گلوگاه را دور می‌زند، اما کارت شبکه را به کلی از کنترل لینوکس خارج می‌کند؛ مسئله‌ای که امکان استفاده از ابزارهای بومی (iproute2، nftables، tcpdump) و سرویس‌های همجوار را سلب می‌نماید.

فناوری لینوکس AF_XDP (شناخته‌شده به عنوان XSK) پاسخی کارآمد برای این چالش ساختاری است. با تلفیق بازهدایت بسته در سطح راه‌انداز شبکه به کمک eBPF و مخازن حافظه از پیش تخصیص‌یافته در فضای کاربری (UMEM)، زیرسیستم AF_XDP انتقال بسته‌ها را بدون نیاز به کپی حافظه به برنامه کاربر می‌رساند و همزمان یکپارچگی سیستم‌عامل لینوکس را حفظ می‌کند.

مقایسه مسیر داده سنتی TUN/TAP و مسیر بدون کپی AF_XDP

برای درک تفاوت عملکرد، جریان گردش بسته رمزگذاری‌شده در هر دو الگو را مرور می‌کنیم:

[مسیر سنتی TUN/TAP]
حلقه دریافت کارت شبکه ──> alloc_skb() ──> پشته شبکه هسته / Netfilter ──> راه انداز TUN
                                                                             │
                     سوئیچ زمینه پردازنده + کپی حافظه (copy_to_user) <────────┘
                                              │
                                              ▼
                             برنامه VPN در فضای کاربر (رمزگشایی)

─────────────────────────────────────────────────────────────────────────

[مسیر داده بدون کپی AF_XDP]
حلقه دریافت کارت شبکه ──> انتقال مستقیم DMA ──> قطعه حافظه ثبت شده UMEM
                                  │
                  فیلتر XDP: bpf_redirect_map()
                                  │
                                  ▼ (بدون کپی و بدون قفل نرم‌افزاری)
                    موتور VPN فضای کاربر (رمزگشایی مستقیم در حافظه)

در جریان پردازش AF_XDP، هسته لینوکس دیگر ساختارهای سنگین struct sk_buff را تولید نمی‌کند. در عوض، کنترلر سخت‌افزاری DMA مستقیماً فریم‌های خام اترنت را درون حافظه پیوسته از پیش ثبت شده در فضای کاربر (UMEM) بارگذاری می‌کند. برنامه eBPF فعال در سطح راه‌انداز شبکه (BPF_PROG_TYPE_XDP) سربرگ بسته را بازبینی می‌کند. اگر بسته متعلق به سرویس VPN باشد، با تابع bpf_redirect_map(&qidconf_map, queue_id, 0) مستقیماً به سوکت XSK روانه می‌شود. سایر بسته‌های شبکه با مقدار XDP_PASS وارد پشته استاندارد شبکه لینوکس می‌شوند.

معماری UMEM و چهار حلقه پردازشی بدون قفل

بخش بنیادی سامانه AF_XDP، ساختار حافظه اشتراکی UMEM است؛ ناحیه‌ای پیوسته از حافظه مجازی که توسط فراخوانی سیستمی setsockopt(..., SOL_XDP, XDP_UMEM_REG, ...) در هسته ثبت می‌شود. این حافظه به فریم‌های هم‌اندازه (عموماً ۲۰۴۸ یا ۴۰۹۶ بایت) تقسیم می‌گردد.

ارتباط میان فضای کاربری و راه‌انداز سخت‌افزار از طریق چهار حلقه ارتباطی تک‌تولیدکننده تک‌مصرف‌کننده (SPSC) و بدون قفل صورت می‌گیرد:

  1. حلقه پرسازی (Fill Ring - تولید در فضای کاربر -> مصرف در هسته): برنامه کاربری آدرس فریم‌های خالی UMEM را درون این حلقه می‌ریزد تا کارت شبکه برای پذیرش ترافیک آماده باشد.
  2. حلقه دریافت (Rx Ring - تولید در هسته -> مصرف در فضای کاربر): پس از ورود بسته، راه‌انداز شبکه توصیف‌گر فریم (شامل جابجایی آدرس و طول بسته) را به حلقه دریافت ارسال می‌کند.
  3. حلقه ارسال (Tx Ring - تولید در فضای کاربر -> مصرف در هسته): برای گسیل داده‌های رمزگذاری‌شده خروجی، فضای کاربری فریم‌های تکمیل‌شده را به حلقه ارسال می‌سپارد.
  4. حلقه تکمیل (Completion Ring - تولید در هسته -> مصرف در فضای کاربر): پس از ارسال موفق بسته فیزیکی روی کابل شبکه، راه‌انداز آدرس فریم را در حلقه تکمیل قرار می‌دهد تا بافر بلافاصله بازیافت شود.

از آنجا که این حلقه‌ها بر مبنای شمارنده‌های اتمیک کار می‌کنند، هسته‌های پردازنده بسته‌ها را به شکل دسته‌ای (مثلاً ۳۲ یا ۶۴ تایی) پردازش کرده و رقابت بر سر قفل‌های همگام‌سازی به صفر می‌رسد.

پیاده‌سازی هسته: بازهدایت ترافیک با برنامه eBPF در راه‌انداز

بخش فعال در هسته لینوکس با یک ارزیاب سبک eBPF متصل به رابط فیزیکی پیکربندی می‌شود:

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_XSKMAP);
    __uint(max_entries, 64);
    __type(key, __u32);
    __type(value, __u32);
} qidconf_map SEC(".maps");

SEC("xdp")
int xdp_vpn_redirect(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;
    
    /* بررسی مرز بافر برای سربرگ اترنت */
    if (data + sizeof(struct ethhdr) > data_end)
        return XDP_PASS;
        
    struct ethhdr *eth = data;
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;
        
    struct iphdr *iph = data + sizeof(struct ethhdr);
    if ((void *)(iph + 1) > data_end || iph->protocol != IPPROTO_UDP)
        return XDP_PASS;
        
    struct udphdr *udph = (void *)iph + (iph->ihl * 4);
    if ((void *)(udph + 1) > data_end)
        return XDP_PASS;
        
    /* هدایت مستقیم بسته‌های پورت VPN به سوکت AF_XDP */
    if (udph->dest == __constant_htons(51820)) {
        return bpf_redirect_map(&qidconf_map, ctx->rx_queue_index, XDP_PASS);
    }
    
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

این برنامه در مدت چند نانوثانیه در متن وقفه راه‌انداز سخت‌افزار اجرا می‌شود. ترافیک VPN بی‌درنگ به حلقه‌های کاربری هدایت شده و نشست‌های مدیریتی SSH و مسیریابی BGP طبق روال عادی در شبکه سیستم‌عامل جاری می‌مانند.

موتور فضای کاربری: راه‌اندازی UMEM و سوکت بدون کپی

در سطح برنامه، آماده‌سازی پردازشگرهای AF_XDP مستلزم تخصیص حافظه هماهنگ با صفحه از طریق posix_memalign() و ثبت سوکت با پرچم‌های بهینه‌ساز است:

#include <xdp/xsk.h>
#include <stdlib.h>
#include <sys/socket.h>

#define NUM_FRAMES 4096
#define FRAME_SIZE 2048

struct xsk_umem_info {
    struct xsk_ring_prod fq;
    struct xsk_ring_cons cq;
    struct xsk_umem *umem;
    void *buffer;
};

int init_af_xdp_socket(struct xsk_umem_info *umem_info, const char *ifname, int queue_id)
{
    struct xsk_socket_config xsk_cfg;
    struct xsk_umem_config umem_cfg = {
        .fill_size = NUM_FRAMES,
        .comp_size = NUM_FRAMES,
        .frame_size = FRAME_SIZE,
        .frame_headroom = XDP_PACKET_HEADROOM,
        .flags = 0
    };
    
    /* تخصیص بافر پیوسته و هم‌راستا با صفحات سیستم */
    posix_memalign(&umem_info->buffer, getpagesize(), NUM_FRAMES * FRAME_SIZE);
    
    /* ثبت مخزن UMEM در ساختار هسته سیستم‌عامل */
    xsk_umem__create(&umem_info->umem, umem_info->buffer, 
                     NUM_FRAMES * FRAME_SIZE, &umem_info->fq, 
                     &umem_info->cq, &umem_cfg);
                     
    xsk_cfg.rx_size = NUM_FRAMES;
    xsk_cfg.tx_size = NUM_FRAMES;
    xsk_cfg.libbpf_flags = 0;
    xsk_cfg.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
    xsk_cfg.bind_flags = XDP_ZEROCOPY | XDP_USE_NEED_WAKEUP;
    
    return 0;
}

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

عملکرد عملیاتی در سرورهای لبه Anycast سرویس RiceGuard

سرویس RiceGuard معماری AF_XDP را در گره‌های توزیع‌شده Anycast خود برای تثبیت کارایی در تراکم بالا مستقر نموده است:

  • تراکم بالای ارسال بسته: گره‌های محاسباتی RiceGuard به ظرفیت پردازش بیش از ۲۴ میلیون بسته در ثانیه (Mpps) دست یافته‌اند که نسبت به تونل‌های TUN رشد چشمگیری دارد.
  • تأخیر پایدار در مقیاس میکروثانیه: با حذف قفل‌های پشته شبکه و کپی‌های مکرر حافظه، تأخیر زمانی پردازش بسته زیر ۱.۲ میکروثانیه پایدار می‌ماند.
  • حفظ ابزارهای مدیریتی لینوکس: رابط شبکه همچنان تحت اشراف لینوکس کار می‌کند، بنابراین پروتکل مسیریابی Anycast BGP و پایشگرهای شبکه بدون اختلال در کنار مسیر شتاب‌یافته فعال هستند.

ترکیب هوشمندانه فناوری AF_XDP و حلقه‌های پردازشی UMEM، توان محاسباتی تبادل داده در پروتکل‌های رمزگذاری را در مرز فیزیکی کارت شبکه قرار می‌دهد و ساختار چندمنظوره لینوکس را تماماً در دسترس نگه می‌دارد.