هنگامی که یک رابط شبکه فعال ناگهان قطع می‌شود—مانند بسته شدن درب لپ‌تاپ، جابه‌جایی گوشی همراه میان دکل‌های مخابراتی یا قطع فیبرهای نوری در مسیرهای زیرساختی—برنامه‌ها برای آگاهی از وضعیت شبکه کاملاً به تایمرهای لایه انتقال متکی هستند. در سامانه‌های توزیع‌شده، مخازن اتصال پایگاه داده و تونل‌های رمزنگاری‌شده VPN، اتکا به مقادیر پیش‌فرض سیستم‌عامل اغلب به یخ‌زدگی و توقف کامل سوکت‌ها به مدت ۱۵ تا ۳۰ دقیقه منجر می‌شود.

سیستم‌عامل پایداری اتصالات را از طریق دو سازوکار مجزا مدیریت می‌کند: بسته‌های کاوشگر TCP Keepalive در زمان سکوت شبکه، و تایمر تأخیر نمایی بازفرستادن (Retransmission Timeout - RTO) در زمان وجود داده‌های تأییدنشده. به دلیل مرزبندی‌های معماری در مشخصات اولیه TCP، این دو بخش کاملاً مستقل عمل می‌کنند و نقطه کوری ایجاد می‌نمایند که در آن اتصالات قطع‌شده بی‌پاسخ رها می‌شوند. برای رفع ساختاری این نقص، استاندارد IETF RFC 5482 گزینه مهلت زمانی کاربر (TCP_USER_TIMEOUT) را پایه‌گذاری کرد.

نقص معماری: چرا TCP Keepalive هنگام تبادل ترافیک ناتوان است

یک باور نادرست رایج در میان مدیران سیستم این است که فعال‌سازی پرچم SO_KEEPALIVE تضمین‌کننده شناسایی سریع قطعی شبکه است. در عمل، طبق تصریح استاندارد RFC 1122، این سازوکار محدودیت شدیدی دارد: بسته‌های کاوشگر تنها زمانی ارسال می‌شوند که سوکت کاملاً بیکار باشد و تمام داده‌های ارسالی قبلاً توسط گیرنده تأیید شده باشند (snd_una == snd_nxt).

                ┌─────────────────────────────────┐
                │         سوکت فعال TCP           │
                └────────────────┬────────────────┘
                                 │
                   آیا داده تأییدنشده در جریان است؟
                                 │
                 ┌───────────────┴───────────────┐
                 │ بله                           │ خیر
                 ▼                               ▼
    [ تایمر بازفرستادن RTO فعال می‌شود ] [ تایمر Keepalive فعال می‌شود ]
    - پرچم SO_KEEPALIVE نادیده گرفته می‌شود - ارسال کاوشگرهای خالی ACK
    - دفعات تلاش: tcp_retries2 (پیش‌فرض ۱۵) - زمان بیکاری: ۷۲۰۰ ثانیه
    - زمان قفل ماندن: ۱۳ تا ۳۰ دقیقه      - کاوشگر: ۹ بار × ۷۵ ثانیه
    - مسدود شدن پردازش‌های برنامه        - کل زمان: حدود ۲ ساعت و ۱۱ دقیقه

اگر برنامه‌ای داده‌ای ارسال کند و درست در همان لحظه لینک فیزیکی یا تونل VPN قطع شود، بسته‌ها در صف خروجی متوقف می‌شوند. از آنجا که بایت‌های تأییدنشده وجود دارند، پشته TCP تایمر Keepalive را خاموش کرده و شمارنده بازفرستادن را فعال می‌سازد.

در تنظیمات پیش‌فرض هسته لینوکس (net.ipv4.tcp_retries2 = 15)، پشته یک زنجیره تأخیر نمایی را از حدود ۲۰۰ میلی‌ثانیه آغاز کرده و به طور متوالی دو برابر می‌کند:

$$\text{RTO}{i} = \min\left(\text{RTO}{i-1} \times 2, 120\text{ s}\right)$$

پس از ۱۵ بار تلاش مجدد، کل زمان تلف‌شده به حدود ۹۲۴ ثانیه (بیش از ۱۵ دقیقه) می‌رسد تا در نهایت هسته وضعیت سوکت را مرده اعلام کرده و خطای ETIMEDOUT برگرداند. در طول این مدت:

  1. مسدود شدن نخ‌های پردازشی: پردازش‌های همگام برنامه که در انتظار پاسخ سوکت هستند مسدود مانده و منابع سرور را هدر می‌دهند.
  2. اشباع مخازن اتصال: دروازه‌های پراکسی و سرویس‌های پشتیبان درخواست‌های جدید را به سمت سوکت‌های از کار افتاده ارسال می‌کنند.
  3. تأخیر در سوئیچ اضطراری: برنامه‌ها به جای آزادسازی اتصال و انتقال به تونل پشتیبان، همچنان داده‌ها را به رابط قطع‌شده سرازیر می‌کنند.

نحوه عملکرد استاندارد RFC 5482 و گزینه TCP_USER_TIMEOUT

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

در هسته لینوکس از نسخه 2.6.37 به بعد، این قابلیت از طریق گزینه سوکت TCP_USER_TIMEOUT در مسیر <netinet/tcp.h> پیاده‌سازی شده است.

پیاده‌سازی در هسته لینوکس: inet_csk(sk)->icsk_user_timeout

هنگامی که برنامه‌ای مقدار TCP_USER_TIMEOUT را تعیین می‌کند، هسته سقف زمانی مورد نظر (بر حسب میلی‌ثانیه) را در ساختار سوکت ذخیره می‌نماید:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int set_tcp_user_timeout(int sockfd, unsigned int timeout_ms)
{
    return setsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, 
                      &timeout_ms, sizeof(timeout_ms));
}

تایمر بازفرستادن هسته (tcp_write_timer()) در هر مرحله از بررسی، مقدار مذکور را از طریق تابع retransmits_timed_out() می‌سنجد:

static bool retransmits_timed_out(struct sock *sk, unsigned int boundary, 
                                  unsigned int timeout)
{
    unsigned int linear_backoff_thresh, start_ts;
    
    if (!inet_csk(sk)->icsk_user_timeout)
        return false;
        
    start_ts = tcp_sk(sk)->retrans_stamp;
    if (tcp_time_stamp(tcp_sk(sk)) - start_ts >= inet_csk(sk)->icsk_user_timeout)
        return true; /* قطع فوری اتصال */
        
    return false;
}

با تنظیم icsk_user_timeout، هسته محاسبات سنتی تکرار تلاش‌ها را نادیده می‌گیرد. به محض اینکه زمان سپری‌شده از نخستین بسته تأییدنشده از سقف مجاز فراتر رود، تلاش‌های مجدد متوقف شده و خطای ETIMEDOUT به برنامه تحویل داده می‌شود.

یکپارچه‌سازی TCP_USER_TIMEOUT با کاوشگرهای Keepalive

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

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

  1. قطعی در حالت سکوت: تایمر Keepalive پس از وقفه کوتاهی (مثلاً ۱۰ ثانیه) بسته کاوشگر ارسال می‌کند. با ارسال این بسته، سوکت از حالت سکوت به حالت داده در جریان تبدیل می‌شود.
  2. ورود مهلت زمانی کاربر: به دلیل وجود بسته در مسیر، TCP_USER_TIMEOUT وارد عمل می‌شود. در صورت عدم دریافت ACK ظرف چند ثانیه (مثلاً ۵ ثانیه)، هسته بلافاصله سوکت را می‌بندد.
پارامتر پیش‌فرض لینوکس تنظیم بهینه‌سازی‌شده ترکیب هماهنگ RFC 5482
SO_KEEPALIVE غیرفعال (0) فعال (1) فعال (1)
TCP_KEEPIDLE ۷۲۰۰ ثانیه (۲ ساعت) ۳۰ ثانیه ۱۰ ثانیه
TCP_KEEPINTVL ۷۵ ثانیه ۵ ثانیه ۳ ثانیه
TCP_KEEPCNT ۹ کاوشگر ۳ کاوشگر ۳ کاوشگر
TCP_USER_TIMEOUT 0 (غیرفعال) 0 (غیرفعال) ۹۰۰۰ میلی‌ثانیه (۹ ثانیه)
زمان کشف قطعی (ترافیک فعال) ۱۳ تا ۳۰ دقیقه ۱۳ تا ۳۰ دقیقه ۹٫۰ ثانیه
زمان کشف قطعی (حالت سکوت) ۷۸۷۵ ثانیه ۴۵ ثانیه ۱۲٫۰ ثانیه

همان‌طور که در جدول مقایسه مشخص است، بهره‌گیری از TCP_USER_TIMEOUT زمان تشخیص قطعی را از نیم ساعت به ۹ ثانیه کاهش می‌دهد؛ بدون توجه به اینکه داده در حال انتقال بوده یا شبکه ساکت بوده است.

اثرگذاری بر تونل‌های رمزنگاری‌شده VPN و رابط‌های مجازی TUN

یخ‌زدگی سوکت‌ها پیامدهای سنگینی در شبکه‌های خصوصی مجازی VPN به همراه دارد. در محیط‌های مبتنی بر رابط‌های مجازی (tun0 یا wg0)، بسته‌بندی داده‌ها پیوندی دوگانه میان جریان‌های داخلی و حامل خارجی ایجاد می‌کند.

[ سوکت برنامه کاربردی HTTP / RPC ] ── (جریان داخلی TCP)
                 │
                 ▼
[ رابط مجازی شبکه: tun0 / wg0 ] ───── (کپسوله‌سازی و رمزنگاری)
                 │
                 ▼
[ حامل خارجی تونل UDP ] ──────────── (رابط فیزیکی Wi-Fi / اینترنت همراه)

سناریوی بن‌بست ارتباطی

هنگامی که کاربر متحرک ارتباط خود را از دست می‌دهد:

  1. حامل بیرونی UDP سیگنال فیزیکی را از دست می‌دهد یا در کاوشگرهای DPD با شکست مواجه می‌شود.
  2. با این حال، سوکت‌های داخلی TCP که توسط برنامه‌ها باز شده‌اند، در صورت عدم دریافت پالس تغییر مسیر، از قطعی لینک بیرونی بی‌خبر می‌مانند.
  3. در صورت تلاش نرم‌افزار برای ارسال داده، پشته داخلی تا ۱۵ دقیقه بیهوده به ارسال مجدد ادامه می‌دهد.

مدیریت جابه‌جایی سریع در ساختار RiceGuard

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

با ترکیب راهبردهای مسیریابی Core L3 VPN و کنترل دقیق سوکت‌ها، پروتکل RiceGuard سقف زمانی معینی را برای ارتباطات تعیین می‌نماید. هنگام قطع ناگهانی لینک فیزیکی:

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

پیکربندی عملیاتی و پایش در لینوکس

مهندسان می‌توانند عملکرد TCP_USER_TIMEOUT را در کدهای برنامه‌نویسی یا از طریق ابزارهای ترمینال ارزیابی کنند.

تنظیم مهلت زمانی در کدهای برنامه

در زبان برنامه‌نویسی Go، تنظیم این پارامتر از طریق قلاب کنترلی net.Dialer میسر است:

package main

import (
    "net"
    "syscall"
    "golang.org/x/sys/unix"
)

func createResilientDialer(timeoutMs int) *net.Dialer {
    return &net.Dialer{
        Control: func(network, address string, c syscall.RawConn) error {
            return c.Control(func(fd uintptr) {
                // فعال‌سازی TCP_USER_TIMEOUT
                _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, 
                                       unix.TCP_USER_TIMEOUT, timeoutMs)
                // فعال‌سازی SO_KEEPALIVE
                _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, 
                                       unix.SO_KEEPALIVE, 1)
            })
        },
    }
}

پایش بلادرنگ وضعیت سوکت با ابزار ss

ابزار ss در لینوکس جزئیات دقیق تایمرهای سوکت را از هسته استخراج می‌کند:

# بررسی پارامترهای تایمر سوکت‌های فعال روی درگاه 443
ss -tin '( sport = :443 or dport = :443 )'

شاخص‌های کلیدی در خروجی:

  • timer:(retrans,...): وضعیت تایمر بازفرستادن و مدت زمان سپری‌شده.
  • timeout:<ms>: مقدار فعال TCP_USER_TIMEOUT روی سوکت.
  • rto:<ms>: مهلت بازفرستادن برآوردشده فعلی.

نمایش فیلد timeout نشان می‌دهد هسته تضمین کرده که اتصال هرگز فراتر از مرز میلی‌ثانیه‌ای مشخص دوام نخواهد آورد.

جمع‌بندی: پایه‌ای استوار برای شبکه‌های تاب‌آور

به‌کارگیری انحصاری TCP Keepalive قادر به محافظت از سیستم در برابر قطعی‌های حین ارسال داده نیست. با ایجاد یک سقف قاطع بر زمان انتظار تأیید بسته‌ها، استاندارد IETF RFC 5482 شکاف میان کاوشگرهای بیکاری و بازفرستادن ناموفق را پر کرد.

برای شبکه‌های رمزنگاری‌شده VPN و پایانه‌های متحرک، پیاده‌سازی TCP_USER_TIMEOUT اتصالات شکننده را به کانال‌هایی مطمئن با قابلیت قطع سریع و بازیابی آنی تبدیل کرده و تداوم ارتباطات را حفظ می‌نماید.