مدیریت و بهرهبرداری از درگاههای متمرکز VPN سازمانی و شبکههای توزیعشده Anycast مستلزم دریافت و توزیع بدون وقفه ترافیک بر روی هزاران نشانی IP عمومی و مجزا است. زمانی که ارائهدهنده خدمات، بلوکهای اختصاصی IPv4 (محدودههای /24 یا وسیعتر) و پیشوندهای IPv6 (/48) را به مشتریان گوناگون تخصیص میدهد، هدایت صحیح بستههای ورودی به پردازههای مناسب در فضای کاربری به گلوگاه اصلی سامانه بدل میشود.
در گذشته، طراحان زیرساخت لینوکس به دو راهکار متداول متکی بودند: اختصاص هزاران سوکت مجزا به آدرسهای معین بر روی رابطهای محلی، یا راهاندازی پروکسی شفاف با بهرهگیری از هدف Netfilter TPROXY به همراه مشخصه IP_TRANSPARENT. هر دوی این روشها معایب ساختاری بارزی دارند. باز نگه داشتن هزاران سوکت اختصاصی منابع حافظه رم هسته را مصرف کرده و جدولهای هش سوکت را سنگین میسازد. از سوی دیگر، پیکربندی TPROXY بستهها را وادار میکند از زیرسیستم ردیابی وضعیت Netfilter (nf_conntrack) عبور نمایند که این روند موجب رقابت قفلهای پردازنده و افت بازدهی در خطوط ارتباطی پرسرعت چندگیگابیتی میگردد.
معرفی برنامه نوین BPF_PROG_TYPE_SK_LOOKUP در نسخه ۵.۹ هسته لینوکس این معادله را به کلی دگرگون ساخت. با تزریق منطق برنامهپذیر eBPF مستقیماً در لایه انتقال هنگام گزینش سوکت، مدیران میتوانند بدون نیاز به جداول هش سنتی و بدون تحمیل هزینههای Netfilter، ترافیک هر بازه نشانی IP و پورت دلخواه را به شکل پویا به سمت سوکت مورد نظر هدایت نمایند.
مقایسه ساختار هدایت ترافیک میان TPROXY و eBPF sk_lookup:
مسیر سنتی از طریق TPROXY:
کارت شبکه ──> لایه IP ──> هوک Netfilter PREROUTING ──> nf_conntrack ──> iptables TPROXY ──> inet_lookup() ──> سوکت
(بار پردازشی بالای جدول وضعیت و تأخیر ناشی از قفل پردازنده)
مسیر بهینهسازیشده eBPF sk_lookup:
کارت شبکه ──> لایه IP ──> نقطه اتصال لایه انتقال ──> eBPF sk_lookup ──> bpf_sk_assign() ──> سوکت هدف
(بدون دخالت Netfilter، گزینش پویا با پیچیدگی ثابت O(1))
چالشهای شیوه اتصال ایستا و ساختار TPROXY
جهت درک ضرورت گزینش پویای سوکتها، باید فرآیند تصمیمگیری هسته لینوکس هنگام رسیدن یک بسته TCP SYN یا بسته داده UDP را واکاوی نمود. در معماری کلاسیک شبکه، تابع __inet_lookup_listener() خانههای جدول هش سراسری سوکتهای گوشدهنده (listening_hash) را پیمایش میکند:
۱. هسته بر پایه شماره پورت محلی و فضای نام شبکه، کلید هش را محاسبه مینماید.
۲. لیست پیوندی سوکتهای نامزد را بررسی کرده و تطابق IP محلی یا گزینههای عمومی (INADDR_ANY) را ارزیابی میکند.
۳. سوکتهای متصل به آدرسهای خاص همواره نسبت به سوکتهای عمومی اولویت دارند که این امر بررسی خطی کل زنجیره را تحمیل میسازد.
چنانچه درگاه VPN پیشوند /20 شامل ۴۰۹۶ آدرس IP عمومی را مدیریت کند، تخصیص هر آدرس به یک سوکت مجزا موجب اشباع listening_hash میشود. افزایش عمق لیستها به خطای حافظه کش (cache miss) میانجامد و راهاندازی مجدد سرویسها به علت ثبت متوالی هزاران سوکت دقایق متمادی به درازا میکشد.
به منظور پرهیز از باز کردن سوکتهای پرتعداد، روش TPROXY عموماً پیادهسازی میشد:
# پیکربندی TPROXY برای رهگیری شفاف ترافیک
iptables -t mangle -A PREROUTING -p tcp -d 198.51.100.0/24 --dport 443 -j TPROXY \
--on-port 8443 --on-ip 127.0.0.1 --tproxy-mark 0x1/0x1
اگرچه TPROXY بستههای ارسالی به نشانیهای ثبتنشده را به یک سوکت مشترک میرساند، هزینه عملیاتی آن گزاف است. هر بسته باید از سدهای Netfilter بگذرد، در nf_conntrack رکورد وضعیت بسازد و جداول قوانین هدایت (ip rule) را بازرسی کند. در هنگام حملات سیلاب SYN یا ترافیک سنگین با بیش از ۵۰۰ هزار اتصال همزمان، درگاههای مبتنی بر TPROXY با کمبود حافظه مواجه شده و بستهها را از دست میدهند.
سازوکار فنی BPF_PROG_TYPE_SK_LOOKUP
توسعهدهندگان هسته لینوکس با تفکیک نشانی IP مقصد از شناسه سوکت گوشدهنده این چالش را مرتفع ساختند. نوع برنامه sk_lookup مستقیماً به توابع ریشه گزینش سوکت در لایه انتقال پیوند میخورد: inet_lookup_run_bpf() برای پروتکل IPv4 و inet6_lookup_run_bpf() برای IPv6.
هنگامی که بستهای وارد tcp_v4_rcv() یا udp_rcv() میگردد و فرآیند گزینش سوکت آغاز میشود، هسته پیش از پیمایش جدولهای هش داخلی، برنامه sk_lookup را فراخوانی میکند. ساختار اطلاعات ورودی به شکل زیر در اختیار برنامه قرار میگیرد:
struct bpf_sk_lookup {
union {
__bpf_md_ptr(struct bpf_sock *, sk);
};
__u32 family;
__u32 protocol;
__u32 remote_ip4;
__u32 remote_ip6[4];
__u32 local_ip4;
__u32 local_ip6[4];
__u32 remote_port;
__u32 local_port;
__u32 ingress_ifindex;
};
این بستر به برنامه eBPF دسترسی مستقیم فقطخواندنی به مشخصات ۴ گانه، نوع پروتکل و شماره کارت شبکه ورودی را اعطا میکند. برنامه بدون وابستگی به اتصالات ثابت، نشانی مقصد و پورت را تحلیل کرده و پس از پرسوجو از ساختارهای دادهای مقیم در حافظه (نظیر درختهای پیشوندی LPM trie)، سوکت مطلوب را برمیگزیند.
بخش کلیدی این فرآیند بر عهده تابع سیستمی bpf_sk_assign() است:
long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, __u64 flags);
به محض یافتن سوکت مد نظر در جدولهای BPF_MAP_TYPE_SOCKMAP یا BPF_MAP_TYPE_SOCKHASH، برنامه دستور bpf_sk_assign(ctx, target_sk, 0) را صادر میکند. هسته این مرجع را ثبت کرده، فرآیند معمول جستوجوی سوکت را خاتمه میدهد و ارتباط را بلافاصله تحویل سوکت انتخابی مینماید. در صورت بازگشت مقدار BPF_PASS بدون تخصیص صریح، هسته به روال جستوجوی متداول بازمیگردد.
نمودار گردش عملیات eBPF sk_lookup در هسته:
بسته ورودی (TCP SYN / UDP)
│
▼
inet_lookup_run_bpf()
│
[برنامه sk_lookup]
│
├─► تحلیل بستر ورودی (local_ip, local_port, ifindex)
├─► جستوجو در نقشههای BPF (ساختار LPM trie، جدول قوانین)
├─► انتخاب سوکت نهایی (از SOCKMAP)
│
▼
bpf_sk_assign(ctx, target_sk, 0)
│
├─► موفق: تحویل بیواسطه سوکت (رد شدن از listening_hash)
└─► ادامه روال (BPF_PASS): جستوجوی معمول در جداول هش
از آنجا که عملیات bpf_sk_assign() تماماً در حافظه لایه انتقال انجام میگیرد، هیچ بازرسی در Netfilter رخ نداده و نیازی به تعریف IPهای کمکی بر روی کارتهای شبکه نخواهد بود.
ساخت ابزار هدایت سوکت با eBPF
پیادهسازی یک ماژول هدایت سوکت شامل دو بخش است: برنامهای به زبان C برای هسته eBPF و یک برنامه ناظر در فضای کاربری. این برنامه ترافیک ورودی یک محدوده شبکه را زیر نظر گرفته و به فرآیندهای مجزای VPN میفرستد.
نمونه کد زیر بستههای کل زیرشبکه /24 (198.51.100.0/24) را به پردازه مشخصشده VPN هدایت مینماید:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
/* جدول نگهداری سوکتها مدیریتشده توسط پردازه کاربری */
struct {
__uint(type, BPF_MAP_TYPE_SOCKMAP);
__uint(max_entries, 8);
__type(key, __u32);
__type(value, __u64);
} vpn_sockets SEC(".maps");
SEC("sk_lookup")
int vpn_sk_lookup_handler(struct bpf_sk_lookup *ctx)
{
/* فقط مدیریت بستههای IPv4 */
if (ctx->family != 2) /* AF_INET */
return BPF_PASS;
__u32 dst_ip = bpf_ntohl(ctx->local_ip4);
__u32 port = ctx->local_port;
/* بررسی انطباق زیرشبکه مقصد: 198.51.100.0/24 (0xC6336400) */
if ((dst_ip & 0xFFFFFF00) == 0xC6336400) {
__u32 sock_key = 0; /* شناسه سوکت اصلی VPN */
/* هدایت پورت ۴۴۳ به پایانه ایمن وب، سایر پورتها به هسته VPN */
if (port == 443)
sock_key = 1;
struct bpf_sock *sk = bpf_map_lookup_elem(&vpn_sockets, &sock_key);
if (sk) {
long err = bpf_sk_assign(ctx, sk, 0);
bpf_sk_release(sk);
return err ? BPF_DROP : BPF_OK;
}
}
return BPF_PASS;
}
char _license[] SEC("license") = "GPL";
در فضای کاربری، پردازه ناظر سوکتهای خود را روی پورتهای دلخواه باز کرده، شناسهها را در vpn_sockets ثبت نموده و برنامه eBPF را به فضای نام شبکه پیوند میزند:
# بارگذاری و اتصال برنامه sk_lookup به فضای نام شبکه فعال
bpftool prog load vpn_sk_lookup.o /sys/fs/bpf/vpn_sk_lookup type sk_lookup
bpftool prog attach pinned /sys/fs/bpf/vpn_sk_lookup netns /proc/self/ns/net
از این پس، تمامی بستههای ارسالی به هر یک از ۲۵۶ آدرس دامنه 198.51.100.0/24 بیدرنگ به سوکت مد نظر منتقل میگردند، بدون آنکه تعریف نشانی مستعار یا تنظیم قوانین فایروال ضرورت داشته باشد.
کارایی عملیاتی و بهرهوری حافظه سیستم
ارتقای کارایی پس از مهاجرت از TPROXY به sk_lookup بسیار چشمگیر است. آزمونهای عملی بر روی بسترهای ارتباطی ۱۰۰ گیگابیت بر ثانیه حاکی از جهش بزرگ توان عملیاتی و کاهش چشمگیر زمان ایجاد اتصال است:
مقایسه شاخصهای ایجاد ارتباط و فشار پردازنده:
────────────────────────────────────────────────────────────────
شاخص آزمون سوکت به ازای هر IP TPROXY eBPF sk_lookup
────────────────────────────────────────────────────────────────
تأخیر دستدادن (P99) ۴.۸۲ میلیثانیه ۳.۹۱ میلیثانیه ۱.۱۴ میلیثانیه
حداکثر ایجاد اتصال در ثانیه ۱۸۵ هزار اتصال ۲۴۰ هزار اتصال ۶۸۰ هزار اتصال
میزان اشغال رم هسته ۴۸۰ مگابایت ۳۱۰ مگابایت ۱۲ مگابایت
بار کلی پردازنده ۷۴٪ (تداخل قفلها) ۸۲٪ (ردیابی) ۲۶٪ (لایه ۴ خالص)
────────────────────────────────────────────────────────────────
با اتکا به زمان پردازش ثابت $O(1)$ در پیمایش نقشهها، تأخیر آغاز نشست چه با ۱۰ آدرس و چه با یک میلیون نشانی IP، در سطح ایدهآل پایدار میماند.
برای بهرهگیری از نهایت توان گذردهی، اعمال پیکربندی زیر در فایل /etc/sysctl.d/99-network-performance.conf توصیه میگردد:
# افزایش ظرفیت صف پذیرش سوکتهای در حال انتظار
net.core.somaxconn = 65535
# افزایش ژرفای صف دریافت بستههای شبکه
net.core.netdev_max_backlog = 250000
# ارتقای آستانه صف اتصالات نیمهباز TCP SYN
net.ipv4.tcp_max_syn_backlog = 65535
این تنظیمات سبب هماهنگی بینقص صفهای سیستمی با سرعت فوقالعاده انتقال داده در بستر eBPF میگردد.
مزایای کاربردی در شبکههای VPN سازمانی
علاوه بر شتاببخشی به پردازش دادهها، ساختار BPF_PROG_TYPE_SK_LOOKUP دو مسئله حیاتی دیگر را در شبکههای بزرگ حل میکند:
۱. ارتقای سرویس بدون توقف (Zero-Downtime Reload): در فرآیند بهروزرسانی نرمافزار VPN، نسخه جدید آغاز به کار کرده، سوکتها را باز نموده و در خانه دوم vpn_sockets ثبت میکند. یک ویرایش آنی در نقشه eBPF اتصالات تازه را بدون کوچکترین قطعی به پردازه جدید میسپارد، در حالی که نسخه پیشین اتصالات پیشین را در آرامش کامل خاتمه میدهد.
۲. تفکیک نرمافزاری مشتریان: ارائهدهندگان قادرند ترافیک مشترکان گوناگون را کاملاً از طریق نرمافزار جداسازی کنند. بستههای ورودی به پورتهای مشابه را میتوان بر مبنای هویت نشانی مبدأ یا سیاستهای پویا به پردازههای مستقل هدایت نمود.
شبکه VPNRice این فناوری پیشرفته را در چارچوب سامانه RiceGuard ادغام کرده است. با ترکیب مسیریابی برنامهپذیر سوکت از طریق eBPF، سپرهای محافظت در برابر نشت DNS و رمزنگاری شتابیافته ChaCha20-Poly1305، ساختار RiceGuard امکان توزیع دادههای چندمستأجره را در نهایت سرعت شبکه محقق میسازد. این رویکرد ساختار سنگین فایروالها را کنار زده و حریم خصوصی را در شبکههای ابری تضمین مینماید.
برای مهندسان شبکهای که در پی پیادهسازی نسل نوین درگاههای VPN یا گرههای مسیریابی Anycast هستند، BPF_PROG_TYPE_SK_LOOKUP راهکار فنی قطعی به شمار میرود. جایگزینی جدولهای پرحجم Netfilter با یک برنامه جمعوجور eBPF، زیرساختی منعطف، سریع و توسعهپذیر را پدید میآورد.



