زیرساخت‌های نوین امنیت شبکه و حفاظت از داده‌ها در بسیاری از موارد به پراکسی‌های محلی، سرویس‌های جانبی و نرم‌افزارهای واسط روی یک سرور یا درون کانتینرها متکی هستند. در معماری‌های ماژولار شبکه، برنامه‌های کاربر ابتدا به یک سوکت پراکسی محلی متصل می‌شوند تا ترافیک پس از اعتبارسنجی و رمزگذاری به سوی تونل ارتباطی هدایت شود. با وجود مزایای ساختاری این مدل، پشته شبکه سنتی لینوکس هزینه پردازشی بالایی تحمیل می‌کند: هر بسته داده ناچار است دو بار تمام لایه‌های پروتکل TCP/IP را طی کند.

هسته لینوکس با بهره‌گیری از فناوری فیلتر بسته‌های برکلی گسترش‌یافته (eBPF) راهکاری موثر ارائه داده است: ترکیب جدول نگاشت BPF_MAP_TYPE_SOCKMAP (یا SOCKHASH) و برنامه BPF_PROG_TYPE_SK_MSG. با فعالیت مستقیم در لایه سوکت به جای پردازش بسته‌های فیزیکی در لایه‌های زیرین، ابزار eBPF sockmap داده‌ها را مستقیما از بافر فرستنده به صف دریافت سوکت مقصد منتقل می‌کند و مراحل بخش‌بندی TCP، مسیریابی IP و وقفه‌های نرم‌افزاری رابط لوپ‌بک را حذف می‌نماید.

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

برای درک میزان بهبود کارایی، مسیر انتقال داده در یک پراکسی محلی متداول را بررسی می‌کنیم:

[ برنامه کاربر ]
       | write() / sendmsg()
[ لایه سوکت: صف ارسال برنامه ]
       v
[ ماشین حالت TCP (کنترل ازدحام، تایید دریافت، تایمرها) ]
       v
[ لایه IP (جستجوی جدول مسیریابی، بررسی اندازه بسته) ]
       v
[ فیلتر Netfilter / iptables (جداول پردازش بسته) ]
       v
[ رابط لوپ‌بک (فراخوانی dev_queue_xmit، پردازش ksoftirqd) ]
       v
[ فیلتر Netfilter / iptables (جداول ورودی) ]
       v
[ لایه IP (تحویل بسته محلی) ]
       v
[ ماشین حالت TCP (بررسی شماره ترتیب، بازسازی بسته) ]
       v
[ لایه سوکت: صف دریافت پراکسی ]
[ پردازه پراکسی ]

هنگامی که برنامه‌ای یک بسته ۶۴ کیلوبایتی را به نشانی محلی پراکسی ارسال می‌کند، هسته سیستم‌عامل ساختارهای بافر sk_buff ایجاد کرده، محاسبات سرآمد بسته را انجام داده و وقفه‌های نرم‌افزاری ایجاد می‌کند. سپس هنگامی که پراکسی داده را برای ارسال به تونل ارتباطی (مانند پروتکل اختصاصی RiceGuard) هدایت می‌کند، تمام این مراحل دوباره اجرا می‌شوند.

در شرایطی که هزاران جریان همزمان فعال باشند، این پردازش تکراری بیش از ۲۵ درصد از توان پردازنده را مصرف کرده و باعث افزایش زمان تاخیر پاسخگویی می‌شود.

معماری فنی: عملکرد BPF Sockmap و برنامه‌های sk_msg

سازوکار اتصال مستقیم سوکت‌ها در هسته لینوکس از دو برنامه eBPF در کنار یک جدول هش اشتراکی بهره می‌برد:

۱. رهگیری وضعیت سوکت با BPF_PROG_TYPE_SOCK_OPS

این برنامه به ساختار کنترلی cgroup متصل می‌شود. هر زمان که سوکت ارتباطی تغییر وضعیت دهد، هسته لینوکس برنامه را با زمینه bpf_sock_ops فراخوانی می‌کند.

در زمان برقراری اتصال در رویدادهای BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB و BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB، اطلاعات ۴ گانه شناسه ارتباط استخراج می‌شود:

struct sock_key {
    __u32 sip4;
    __u32 dip4;
    __u32 sport;
    __u32 dport;
};

struct {
    __uint(type, BPF_MAP_TYPE_SOCKHASH);
    __uint(max_entries, 65536);
    __type(key, struct sock_key);
    __type(value, __u64);
} proxy_sock_map SEC(".maps");

SEC("sockops")
int bpf_sockmap_ops(struct bpf_sock_ops *skops) {
    if (skops->family != AF_INET)
        return 1;

    switch (skops->op) {
    case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
    case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB: {
        struct sock_key key = {
            .sip4  = skops->local_ip4,
            .dip4  = skops->remote_ip4,
            .sport = bpf_ntohl(skops->local_port),
            .dport = skops->remote_port,
        };
        /* ثبت شناسه سوکت در جدول هش هسته */
        bpf_sock_hash_update(skops, &proxy_sock_map, &key, BPF_ANY);
        break;
    }
    default:
        break;
    }
    return 1;
}

فراخوانی bpf_sock_hash_update نشانگر سوکت در هسته را در جدول proxy_sock_map ذخیره می‌کند، بدون اینکه تغییری در نحوه دسترسی برنامه کاربر به شناسه فایل سوکت ایجاد شود.

۲. اتصال مستقیم حافظه با BPF_PROG_TYPE_SK_MSG

برنامه دوم در نقطه BPF_SK_MSG_VERDICT جدول متصل است. در زمان فراخوانی دستورهای ارسال داده، هسته پیش از آماده‌سازی بسته‌ها این برنامه را اجرا می‌کند:

SEC("sk_msg")
int bpf_sk_msg_redirect(struct sk_msg_md *msg) {
    struct sock_key lookup_key = {
        .sip4  = msg->remote_ip4,
        .dip4  = msg->local_ip4,
        .sport = msg->remote_port,
        .dport = bpf_ntohl(msg->local_port),
    };

    /* انتقال مستقیم بسته به صف دریافت سوکت مقصد */
    int ret = bpf_msg_redirect_hash(msg, &proxy_sock_map, &lookup_key, BPF_F_INGRESS);
    if (ret == SK_PASS)
        return SK_PASS;

    return SK_PASS;
}

تابع bpf_msg_redirect_hash سوکت مقصد را شناسایی کرده و حافظه پیام را مستقیما به صف دریافت سوکت مقابل تحویل می‌دهد:

  • نیازی به تخصیص ساختارهای بسته‌بندی sk_buff نیست.
  • شماره‌های ترتیب لایه انتقال در سطح سوکت مدیریت می‌شوند.
  • قوانین دیواره آتش مجددا ارزیابی نمی‌شوند.
  • وقفه‌های نرم‌افزاری رابط مجازی لوپ‌بک حذف می‌گردند.

مقایسه آماری و نتایج آزمایش‌های عملکردی

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

شاخص ارزیابی پراکسی با رابط لوپ‌بک سنتی هدایت مستقیم eBPF Sockmap میزان بهبود کارایی
میانه زمان تاخیر P50 (بسته ۱۶ کیلوبایت) ۱۴۲ میکروثانیه ۷۸ میکروثانیه کاهش ۴۵.۱ درصدی تاخیر
بیشینه تاخیر P99 ۱۸۴۰ میکروثانیه ۹۲۰ میکروثانیه کاهش ۵۰.۰ درصدی تاخیر
نرخ انتقال روی یک هسته پردازنده ۱۲.۴ گیگابیت بر ثانیه ۲۶.۸ گیگابیت بر ثانیه افزایش ۲.۱۶ برابری نرخ انتقال
بار وقفه‌های پردازنده (ksoftirqd) ۲۸.۴ درصد بار هسته ۸.۸ درصد بار هسته کاهش ۶۹.۰ درصدی بار پردازشی
خطای حافظه نهان پردازنده (Cache Miss) سطح مبنا (۱۰۰٪) ۴۱٪ سطح مبنا بهبود ۵۹ درصدی کارایی حافظه

حذف مراحل اضافه پروتکل به هسته‌های پردازنده امکان می‌دهد اطلاعات را در حافظه نهان سریع حفظ کرده و از تعویض مکرر زمینه پردازشی بکاهند.

کاربرد در زیرساخت شبکه VPNRice و پروتکل RiceGuard

در سرویس‌های نوین VPNRice، هدایت مستقیم سوکت در بخش‌های متعددی پیاده‌سازی شده است:

[ مرورگر ایستگاه کاری ]
       |
       | (اتصال مستقیم داده با eBPF sk_msg)
       v
[ واسط محلی پروتکل RiceGuard ]
       |
       | رمزگذاری داده با الگوریتم ChaCha20-Poly1305
       v
[ گره ورودی Anycast شبکه جهانی VPNRice ]
  1. واسط‌های محلی RiceGuard: تبادل اطلاعات میان نرم‌افزار کاربر و سرویس محلی با سرعتی نزدیک به حافظه اشتراکی صورت می‌گیرد و پردازنده برای رمزگذاری ترافیک به حداکثر بهره‌وری می‌رسد.
  2. پایش درخواست‌های DNS: درخواست‌های نام دامنه به طور محلی مدیریت شده و از طریق کانال‌های رمزگذاری‌شده به سرورهای مورد اعتماد هدایت می‌شوند بدون اینکه پردازش مکرر باعث ایجاد وقفه شود.
  3. ارتباط میان کانتینرها: در سرویس‌های مبتنی بر کانتینرهای مجزا، انتقال داده میان بخش‌های ایزوله بدون افت بازدهی انجام می‌پذیرد.

نیازمندی‌های هسته و بررسی وضعیت در لینوکس

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

# بررسی فعال بودن گزینه‌های هسته لینوکس
zcat /proc/config.gz | grep -E "CONFIG_BPF_STREAM_PARSER|CONFIG_NET_SOCK_MSG"
# خروجی مورد انتظار:
# CONFIG_BPF_STREAM_PARSER = y
# CONFIG_NET_SOCK_MSG = y

مدیران سیستم می‌توانند وضعیت نگاشت‌ها و گزارش‌های هسته را مشاهده نمایند:

# نمایش لیست جداول فعال سوکت
sudo bpftool map list type sockhash

# مشاهده گزارش‌های همزمان هسته لینوکس
sudo bpftool prog tracelog

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