کانسنتراتورهای پرترافیک VPN، سرورهای پایانی TLS و مسیریاب‌های لبه شبکه پیوسته تحت فشار جریان سنگینی از اتصالات جدید قرار دارند. در شرایطی که سرور در هر ثانیه با ده‌ها هزار تقاضای اتصال ورودی مواجه است، توان عملیاتی یک پروکسی لبه‌ای تنها به قدرت پردازش رمزنگاری یا پهنای باند فیزیکی شبکه وابسته نیست. پایداری کل ساختار بارها بر اثر انبوهی از دست‌دادن‌های بی‌اثر، پویش‌های خودکار پورت و حملات فرسایشی منابع شبیه به Slowloris دچار اختلال جدی می‌شود.

در روند مرسوم شبکه در لینوکس، تکمیل موفقیت‌آمیز فرآیند دست‌دادن سه‌مرحله‌ای (SYN -> SYN-ACK -> ACK) فوراً یک درخواست اتصال موقت را به یک سوکت تمام‌عیار تبدیل می‌کند. هسته این سوکت را درون صف پذیرش (accept queue) قرار داده و از طریق epoll به فضای کاربر اعلام آمادگی می‌نماید. دیمن پروکسی بیدار شده، دستور accept() را اجرا می‌کند، ساختارهای ردیابی اتصال را در فضای کاربر تخصیص می‌دهد و شناسه فایل را درون حلقه رویداد می‌نشاند—اما سپس ناچار است به حالت انتظار برود تا داده‌هایی از سمت کلاینت برسد که ممکن است با تأخیر طولانی بیایند یا هرگز ارسال نشوند.

جهت پایان دادن به این تعویض‌های مداوم و هدردهنده متن پردازنده (CPU Context Switches) و مصون‌سازی فضای کاربر در برابر سیل اتصالات ساکت، لینوکس گزینه سوکت TCP_DEFER_ACCEPT را تدارک دیده است. این سازوکار با ملزم ساختن هسته به تعویق انداختن ارتقای اتصال تا دریافت نخستین بایت از داده‌های کاربردی، پالایش اولیه را به لایه حافظه بسیار سبک هسته منتقل ساخته و خط دفاعی کارآمدی را برای گیت‌وی‌های متراکم VPN به وجود می‌آورد.


بحران دست‌دادن‌های بیکار: بیداری بیهوده Epoll و فرسایش منابع

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

  1. صف نیمه‌باز (SYN Backlog): با دریافت بسته TCP SYN، هسته ساختار کم‌حجم request_sock را (با اندازه حدود ۲۵۶ بایت) تشکیل داده و در حین ارسال پاسخ SYN-ACK آن را در صف نگهداری می‌کند.
  2. صف پذیرش کامل (Accept Queue): به محض اینکه کلاینت بسته ACK نهایی را برمی‌گرداند، هسته ساختار گسترده struct sock را (با اشغال بیش از ۲ کیلوبایت حافظه رم رزرو شده به همراه بافرهای ارسال و دریافت) ایجاد کرده، سوکت را به صف پذیرش انتقال داده و نشانه آمادگی خواندن (EPOLLIN) را صادر می‌نماید.
تنگنای ساختار سنتی صف پذیرش در لینوکس:

کلاینت (Client)                     پشته شبکه در هسته لینوکس            پروکسی در فضای کاربر
  │                                           │                                  │
  │─── SYN ──────────────────────────────────►│                                  │
  │◄── SYN-ACK ───────────────────────────────│ (ایجاد request_sock حدود ۲۵۶ بایت│
  │                                           │                                  │
  │─── ACK (دست‌دادن خالی بدون داده) ────────►│                                  │
  │                                           │──► تخصیص کامل struct sock        │
  │                                           │──► ورود به صف Accept             │
  │                                           │──► صدور سیگنال EPOLLIN ─────────►│
  │                                           │                                  │ (بیداری نخ پردازشی)
  │                                           │◄── accept4() ────────────────────│ (تخصیص ۴ تا ۱۶ کیلوبایت)
  │                                           │                                  │ (انتظار برای داده...)
  │                                           │                                  │
  │... (کلاینت خاموش است یا اسکن می‌کند) ....│..................................│ (نخ مسدود شده است!)

این شیوه مرسوم دو چالش عمده پدید می‌آورد:

  • اتلاف توان پردازنده: نخ‌های کاری مکرراً برای اتصالاتی که هیچ محتوایی ارسال نمی‌کنند بیدار شده و دست به فراخوانی‌های پرهزینه سیستمی می‌زنند.
  • تکثیر توصیف‌کننده‌های بی‌استفاده: کاوشگرهای خودکار پیوند را برقرار ساخته و آن را رها می‌کنند و ظرفیت توصیف‌کننده‌های باز برنامه را به سقف مجاز RLIMIT_NOFILE می‌رسانند.
  • اشباع صف پذیرش: در هجوم ترافیک، دست‌دادن‌های خالی صف را پر می‌کنند و کلاینت‌های معتبر پیش از آنکه فضای کاربر به آنها رسیدگی کند از دست می‌روند.

بررسی عمیق: دگرگونی ماشین حالت TCP هسته توسط TCP_DEFER_ACCEPT

با فعال‌سازی از طریق فراخوانی setsockopt()، پارامتر TCP_DEFER_ACCEPT ارتباط میان چرخه دست‌دادن هسته و رویدادهای فضای کاربر را دگرگون می‌سازد:

int timeout_secs = 30;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

/* هدایت هسته به تعویق انداختن اعلان accept تا رسیدن نخستین داده */
int ret = setsockopt(listen_fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout_secs, sizeof(timeout_secs));

1. تداوم وضعیت SYN_RECV و تعویق در تخصیص سوکت کامل

هنگامی که TCP_DEFER_ACCEPT روی یک سوکت شنونده تنظیم می‌گردد، هسته بسته ACK کلاینت را دریافت می‌کند اما آگاهانه از ارتقای سوکت جلوگیری می‌نماید. اگر این بسته ACK فاقد داده باشد (skb->len == 0)، اتصال در حالت موقت SYN_RECV درون جدول سبک request_sock باقی می‌ماند.

سوکت به یک شیء سنگین struct sock تبدیل نشده و هیچ رویداد EPOLLIN به دیمن ابلاغ نمی‌شود. از منظر کلاینت، دست‌دادن کامل شده است؛ اما روی سرور، این اتصال حافظه بسیار ناچیزی برده و هیچ پردازشی به فضای کاربر تحمیل نمی‌کند.

2. دسترسی بلادرنگ به داده‌ها هنگام فراخوانی accept()

به محض اینکه کلاینت نخستین بسته داده‌ای خود را بفرستد—مانند پیام TLS ClientHello یا تقاضای برقراری ارتباط تونل—هسته آن را با ساختار معلق request_sock تطبیق می‌دهد.

نحوه عملکرد TCP_DEFER_ACCEPT:

کلاینت (Client)                     پشته شبکه در هسته لینوکس            پروکسی در فضای کاربر
  │                                           │                                  │
  │─── SYN ──────────────────────────────────►│ (ساخت request_sock)              │
  │◄── SYN-ACK ───────────────────────────────│                                  │
  │─── ACK (بدون داده) ──────────────────────►│ (باقی‌ماندن در SYN_RECV؛ بی‌صدا) │ (خواب / عدم بار اضافی)
  │                                           │                                  │
  │... (فاصله زمانی: ۵۰۰ میلی‌ثانیه) .........│ (مصرف تنها حدود ۲۵۶ بایت رم)     │ (صفر درصد استفاده CPU)
  │                                           │                                  │
  │─── PSH, ACK (بسته حاوی TLS ClientHello) ─►│──► ارتقا به سوکت کامل ساختاری   │
  │                                           │──► انتقال به صف Accept           │
  │                                           │──► صدور سیگنال EPOLLIN ─────────►│ (بیداری نخ پردازشی)
  │                                           │◄── accept4() ────────────────────│
  │                                           │◄── recv() (تحویل فوری داده)      │

با دریافت داده، هسته بی درنگ اتصال را به وضعیت TCP_ESTABLISHED برده، ساختارهای لازم را شکل می‌دهد و آن را درون صف پذیرش می‌نشاند. وقتی نخ کاری فضای کاربر با رویداد بیدار می‌شود و accept() را صدا می‌زند، داده‌ها پیشاپیش در بافر دریافت سوکت مستقر هستند و فراخوانی recv() بدون کوچک‌ترین تأخیری داده را بازمی‌گرداند.


مهار حملات: خنثی‌سازی Slowloris و پویش‌های پنهان پورت

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

1. مهار فرسایش جدول اتصالات درون ساختارهای سبک Request Sockets

حملات Slowloris با گشودن هزاران پیوند بدون ارسال سرایند پروتکل، سامانه‌ها را فلج می‌کنند. بدون TCP_DEFER_ACCEPT، یک مهاجم با کمترین پهنای باند می‌تواند جدول توصیف‌کننده‌های پروکسی را اشباع سازد.

با به‌کارگیری TCP_DEFER_ACCEPT، اتصالات بی‌اثر مهاجم در جدول request_sock هسته محبوس مانده و هرگز به فضای کاربر راه نمی‌یابند. دیمن شبکه بدون درگیری، تمام منابع خود را برای سرویس‌دهی به کاربران واقعی حفظ می‌کند.

2. ابطال خودکار پس از پایان مهلت و ارسال مجدد SYN-ACK توسط هسته

عددی که به گزینه TCP_DEFER_ACCEPT پاس داده می‌شود بازه مهلت را به ثانیه تعیین می‌کند. اگر کلاینت پس از دست‌دادن در این بازه داده‌ای ارسال نکند، هسته بسته‌های بازپرسی SYN-ACK را برای یادآوری ارسال می‌کند.

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


طرح عملیاتی: پیکربندی TCP_DEFER_ACCEPT در پروکسی‌های عملیاتی

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

/* ایجاد سوکت شنونده مقاوم در محیط عملیاتی */
int setup_hardened_listener(int port, int defer_timeout) {
    int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
    
    int optval = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
    
    /* فعال‌سازی تعویق پذیرش با بازه زمانی معین (مثلاً ۱۵ تا ۳۰ ثانیه) */
    setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &defer_timeout, sizeof(defer_timeout));
    
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(port);
    
    bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(fd, 4096);
    return fd;
}

نکات حائز اهمیت در استقرار:

  • تنظیم دقیق بازه زمانی: در رله‌های عمومی، متغیر defer_timeout را بین ۱۵ تا ۳۰ ثانیه بگذارید. انتخاب بازه‌های بسیار کوتاه (زیر ۵ ثانیه) ممکن است کلاینت‌های معتبر در شبکه‌های همراه پرنوسان را ناخواسته قطع کند.
  • سازگاری پروتکل‌ها: مطمئن شوید پروتکل‌های مقصد ارسال داده را بلافاصله پس از پیوند آغاز می‌کنند. پروتکل‌های شناخته‌شده نظیر TLS، HTTP، SSH و ترافیک WireGuard منطبق بر این الگو عمل می‌کنند.

هم‌افزایی‌های ساختاری: سازگاری با TCP Fast Open، SYN Cookies و مسیریابی لبه

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

  • سازگاری با TCP SYN Cookies: در اوج حملات سنگین SYN flood، هسته به کوکی‌های بدون حالت تکیه می‌کند. با بازگشت تاییدیه از سوی کلاینت، TCP_DEFER_ACCEPT همچنان ساخت سوکت را تا ورود محتوای کاربردی به تعویق می‌اندازد تا حافظه پایدار بماند.
  • مقایسه با TCP Fast Open: در حالی که TFO داده‌ها را درون بسته نخست SYN می‌گنجاند (RFC 7413)، سازوکار TCP_DEFER_ACCEPT اتصالات رایج سه مرحله‌ای را سامان می‌بخشد. تجمیع این دو قابلیت پوشش شتاب‌بخشی را برای نسل‌های گوناگون کلاینت‌ها فراهم می‌کند.

مسیر داده RiceGuard: پالایش بی‌هزینه اتصالات در لبه شبکه

سرویس VPNRice ساختار TCP_DEFER_ACCEPT را به عنوان یکی از اجزای محوری حفاظت از ورودی در رله‌های RiceGuard تعبیه کرده است. در میزبانی تبادلات پرحجم بین‌المللی، ایستادگی در برابر نویزهای عمومی اینترنت و حملات سرریز منابع نقشی حیاتی در پایداری زیرساخت دارد.

با فعال‌سازی تعویق دست‌دادن روی کلیه درگاه‌های ورودی TCP، سرورهای RiceGuard ضمانت می‌کنند که منابع پردازشی تنها به نشست‌های معتبر و فعال اختصاص یابد. پویش‌های تصادفی پورت و پیوندهای ناتمام درون هسته بی‌اثر شده و بدون بار محاسباتی محو می‌شوند. این رویکرد چندلایه موجب ثبات توان عملیاتی، ارتقای بازدهی منابع سرور و پاسداری از حریم خصوصی دیجیتال کاربران در تمامی بسترهای ارتباطی می‌گردد.