Foundationপ্রথম নীতি থেকে
LEVEL 7লেসন ১২/২২অ্যাডভান্সড১ ঘণ্টা

TCP Reliability — ack, retransmission, RTO, fast recovery

TCP Reliability and Retransmission

হারানো packet, উল্টো ক্রম, ভুল সন্দেহ — wire-এর সব অরাজকতার উপর দাঁড়িয়ে TCP কীভাবে ordered byte-stream promise রাখে; EWMA থেকে SACK পর্যন্ত প্রতিটা যন্ত্রাংশ trace সহ।

এই লেসন শেষে আপনি পারবেন

  • Cumulative acknowledgment-এর semantics ব্যাখ্যা করতে পারবেন — একটা ack number কীভাবে 'এর আগের সবকিছু পেয়েছি' বোঝায়, আর out-of-order segment receiver-এর buffer-এ কোথায় অপেক্ষা করে
  • SRTT আর RTTVAR-এর EWMA সূত্র দুটি হাতে-কলমে প্রয়োগ করে RTO হিসাব করতে পারবেন, আর কেন timeout-এ exponential backoff হয় তা যুক্তি দিয়ে বলতে পারবেন
  • Fast retransmit-এর '৩টা duplicate ACK' নিয়মটা packet-timeline আঁকে প্রমাণ করতে পারবেন — এবং RTO-path বনাম fast-path-এর সময়ের পার্থক্য গণনা করতে পারবেন
  • Go-back-N বনাম selective repeat-এর trade-off TCP-র SACK option-এর আলোকে তুলনা করতে পারবেন
  • Sliding window-কে flow control-এর যন্ত্র হিসেবে ব্যাখ্যা করতে পারবেন — receiver advertisement কীভাবে sender-কে থামায়, আর zero-window probe কী কাজ করে

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

আগের লেসনে connection-এর জন্ম দেখলাম — handshake শেষে দুই পক্ষ একমত যে client গুনছে 1001 থেকে, server 5001 থেকে। কিন্তু এখনই আসল প্রশ্ন: IP-র জগৎ তো best-effort — packet হারায়, উল্টে আসে, দুইবার আসে। এই অরাজকতার উপর TCP কীভাবে দাঁড়িয়ে promise রাখে: “তুমি যে ক্রমে পাঠিয়েছ, সেই ক্রমে, একবারই, সবকিছু পাবে।”?

উত্তরের হৃৎপিণ্ড একটা মাত্র ধারণা: ACK-clock। Receiver ঘুম থেকে ডেকে বলে “এই পর্যন্ত পেলাম” — আর সেই ডাকটাই sender-এর ঘড়ির কাঁটা এগোয়। Packet হারালে ডাক আসে না; ডাকের নীরবতাই সংকেত। TCP-র পুরো reliability যন্ত্রটা এই নীরবতা পড়ার তিনটা আলাদা কৌশলের সমষ্টি:

  • Timeout (RTO) — যথেষ্ট নীরবতার পরে নিজেই সন্দেহ করা
  • Duplicate ACK — receiver-এর বারবার একই অভিযোগ শুনে তাড়াতাড়ি সন্দেহ করা
  • SACK — receiver নিজেই map এঁকে দেওয়া “যা আছে তা-ই আছে, ফাঁক এখানে”

আজকের লেসনের শেষে আপনি নিজের লেখা simulator-এ দেখবেন — fast retransmit ঠিক কত সেকেন্ড বাঁচায়, আর কেন RTO-র অপেক্ষা internet- এ মৃত্যুর শাস্তি।

মূল ধারণা

Byte numbering আর cumulative ACK

Handshake-এ দেখেছি: seq = এই segment-এর প্রথম byte-এর নম্বর, ack = যা পেয়েছি তার পরের expected byte। এই “+1”-এর ভেতরেই লুকিয়ে আছে সবচেয়ে গুরুত্বপূর্ণ শব্দ: cumulative।

ACK = 5001 বলার মানে শুধু “5000 পেয়েছি” নয় — “ISN+1 থেকে 5000 পর্যন্ত সবকিছু, কোনো ফাঁক ছাড়া, পেয়েছি।” একটা সংখ্যা দুই হাজার byte-এর প্রাপ্তি-সনদ। সস্তা, নির্ভরযোগ্য — কিন্তু একটা দাম আছে: 5001-এর আগে ফাঁক থাকলে 5200 পেয়েও বলতে পারবে না “5200 পেয়েছি”। Out-of-order segment-গুলো তাই receiver-এর buffer-এ অপেক্ষা করে — ফাঁক ভরলে সঙ্গে সঙ্গে সারি সাজিয়ে application-এ যোগ দেয়।

Sliding window — flow control-এর যন্ত্র

Sender-এর জগৎ তিনটা পয়েন্টারে ভাগ:

১ ──────[ sent & acked ]──────┬──[ sent, unacked ]──┬──[ পাঠানো যাবে ]──► ∞
                              ↑ base                 ↑ next-to-send
                        (lowest unacked)
পাঠানো যাবে শর্তে: next-to-send < base + rwnd

Receiver প্রতিটা ACK-এর সাথে window field-এ ঘোষণা করে: “আমার receive-buffer-এ এখনো এইটুকু জায়গা আছে।” Application ধীর হলে buffer ভরে, window ছোট হয়, sender থমকে যায় — zero-window হলে পুরো বন্ধ। তখন sender মাঝে মাঝে ছোট probe পাঠায় (persist timer) — “এখনো জায়গা হলো?” — নইলে দুই পক্ষ চিরকাল অপেক্ষায় আটকে যেত।

RTT মাপা → RTO বানানো: EWMA গণিত

Retransmission timeout কত হওয়া উচিত? ছোট হলে spurious retransmit (আসলে packet পথে আছে, আমরা তাড়াহুড়োয় আবার পাঠালাম); বড় হলে সত্যিকারের loss-এর পরে অযৌক্তিক অপেক্ষা। সমাধান: RTT মেপে smoothed অনুমান রাখা। RFC 6298-এর সূত্র — প্রতি measurement R'-এর পরে (α = ⅛, β = ¼):

SRTT←(1−α)⋅SRTT+α⋅R′SRTT \leftarrow (1-\alpha)\cdot SRTT + \alpha \cdot R'

RTTVAR←(1−β)⋅RTTVAR+β⋅∣SRTT−R′∣RTTVAR \leftarrow (1-\beta)\cdot RTTVAR + \beta \cdot |SRTT - R'|

RTO←SRTT+G+4⋅RTTVARRTO \leftarrow SRTT + G + 4\cdot RTTVAR

পড়ার নিয়ম: SRTT হলো গড়, RTTVAR হলো গড় থেকে ছড়ানোর মাপ — আর RTO হলো গড় plus four deviations — অর্থাৎ “স্বাভাবিক ঝাঁকুনির চার গুণের বেশি নীরব হলে তবেই সন্দেহ করব।” এই সূত্রটা আসলে EWMA — Exponentially Weighted Moving Average — ঠিক সেই ভারিত গড়, যেখানে পুরনো মান সূচকীয় হারে বিবর্ণ হয়।

Timeout আসলেই লাগলে backoff: প্রতিবার RTO × 2। যুক্তি: timeout মানে network অসুস্থ; অসুস্থ network-এ ধারাবাহিক retransmit ঢেলে দেওয়া আগুনে ঘি ঢালা। Congestion collapse-এর আগের যুগের শিক্ষা (পরের লেসনের মূল কাহিনি)।

Fast retransmit — ৩টা duplicate ACK-র গণিত

RTO-র সমস্যা: এটা অন্ধ — নীরবতা দেখলেই অপেক্ষা করে RTO পর্যন্ত (typical 200ms+)। কিন্তু packet হারালে receiver যা করে সেটা লক্ষ্য করুন:

sender: seg-1  seg-2✗   seg-3   seg-4   seg-5
receiver:  ok    (হারাল)  গর্ত!   গর্ত!   গর্ত!
           ACK-2  —     ACK-2   ACK-2   ACK-2     ← তিনবার একই অভিযোগ!

প্রতিটা out-of-order arrival-এ receiver আবার সেই expected সংখ্যা পাঠায় — duplicate ACK। তিনটা dup-ACK মানে: তিনটা পরের segment সফলভাবে পৌঁছেছে, অথচ ওই একটা ফাঁক — তাহলে packet হারিয়েছে, দেরিতে আসছে না (reordering-এর tolerance হিসেবেই ১ নয়, ৩)। Sender অপেক্ষা না করে সঙ্গে সঙ্গে সেই একটা segment পুনঃপাঠায় — fast retransmit। খরচ: ~1 RTT। RTO-র খরচ: একটা পূর্ণ RTO।

SACK — receiver নিজেই মানচিত্র আঁকে

Cumulative ACK-র জন্মগত অন্ধত্ব: এক ACK-তে একটাই সংখ্যা। SACK option সেই ফাঁক ভরে: ACK-এর সাথে তালিকা —

left edge  right edge    মানে
4501       5000          "4501–5000 আমার কাছে আছে"
5201       5600          "5201–5600-ও আছে"
(implicit: 5001–5200 নেই — ওইটাই ফাঁক!)

Sender তখন শুধু ফাঁকটাই পুনঃপাঠায় — বাকি সব যা আছে তা রেখে দিয়ে।

Go-back-N বনাম Selective repeat — তত্ত্বের কাঠামো

Textbook-এর দুই pole:

Go-back-NSelective Repeat
Loss-এ কী হয়ফাঁকের পরে সব পুনঃপাঠায়শুধু ফাঁকটাই
Receiver bufferফাঁকের পরের সব ফেলে দেয়জমিয়ে রাখে
Sender-এর stateন্যূনতমপ্রতি segment-এর timer
বাস্তব TCPছাড়া SACK: N-ঘেঁষাSACK-সহ: SR-ঘেঁষা

আধুনিক OS-এ SACK default-on — অর্থাৎ আজকের TCP বাস্তবে selective repeat-এর দিকেই ঝোঁকা, যদিও cumulative ACK-র ভাষায় কথা বলেই।

ভেতরে কী ঘটছে

Dup-ACK কবে পাঠায়, কবে পাঠায় না

সূক্ষ্ম নিয়মগুলো যা capture-এ গুলিয়ে ফেলে:

  • Out-of-order segment আসলেই কেবল dup-ACK (same expected)
  • In-order segment আসলে নতুন cumulative ACK — এতে dup গণনা reset
  • Window-full বা delayed-ACK policy-তে কখনো ACK-ই আসে না — Nagle
    • delayed-ACK interaction-এর কুখ্যাত 40ms pause এখান থেকেই
  • Dup-ACK-গুলো নিজে কোনো data consume করে না — শুধু সংকেত

Spurious retransmission — ভুল সন্দেহের দাম

Fast retransmit-এর ৩-dup নিয়ম reordering tolerate করে, পুরোপুরি নয়। Mobile network-এ handover, Wi-Fi roaming, multi-path routing — packet ২–৩ hop পেছনে পড়ে যেতে পারে। তখন sender ভাবে lost, পুনঃপাঠায় — কিন্তু original-ও পরে পৌঁছায়। Receiver দুইবার একই data পায় (dup detection-এ ফেলে দেয়, তবু bandwidth নষ্ট), আর worse: RTT sample-গুলো দূষিত হয়। Karn’s algorithm: retransmitted segment- এর RTT sample কোনোদিন estimation-এ ঢোকে না — কারণ সেই ACK কোন copy-র উত্তর তা বলা যায় না।

Zero-window আর persist timer

Receiver window=0 ঘোষণা করলে sender থেমে যায় — কিন্তু পুরো থেমে থাকলে বিপদ: receiver পরে window খুলে জানাল, সেই update packet হারালে দুই পক্ষ চিরকাল অপেক্ষা (deadlock!)। Sender তাই persist timer-এ বাঁধা — মাঝে মাঝে 1-byte window probe পাঠায়; receiver-এর প্রতিটা উত্তরে বর্তমান window আসেই। নীরব network-এও clock তখন বাজতে থাকে।

উদাহরণ

Timeline ১ — সব ঠিকঠাক (loss নেই)

t=0.000  C→S seg-1..5 (window ভরা)         [seq 1001,1101,1201,1301,1401]
t=0.050  S পায় seg-1 → ACK-1101
t=0.050  S পায় seg-2 → ACK-1201 (cumulative!)
t=0.060  C ACK-1201 পায় → base=3, window slide → seg-6 পাঠায়
...

প্রতিটা ACK base সামনে ঠেলে, window স্লাইড করে, নতুন data-র অনুমতি দেয় — ACK-clock চলছে: প্রতিটা received byte-এর উত্তরে এক byte পাঠানোর অনুমতি।

Timeline ২ — seg-2 হারাল, fast retransmit

t=0.000  C→S seg-1..5          (seg-2 পথে হারাল)
t=0.050  S: seg-1 পেল        → ACK-1101
t=0.060  S: seg-3 পেল (গর্ত 1101!) → DUP-ACK-1101  (#1)
t=0.070  S: seg-4 পেল              → DUP-ACK-1101  (#2)
t=0.075  C: DUP-ACK-1101 #1 পেল — অপেক্ষা...
t=0.080  S: seg-5 পেল              → DUP-ACK-1101  (#3)
t=0.085  C: #3 পেল → ★ FAST RETRANSMIT seg-2
t=0.135  S: seg-2 পেল → buffer-এ 3,4,5 ছিল → সাথে সাথে ACK-1501
t=0.140  C: base=6 → seg-6..10 পাঠায় — সব normal

মোট জরিমানা: প্রায় 1 RTT (০.০৫s)।

Timeline ৩ — একই loss, fast retransmit বন্ধ

t=0.000  C→S seg-1..5          (seg-2 হারাল)
t=0.06..0.09  DUP-ACK-1101 ×3  → C উপেক্ষা করে (fast off)
t=0.500  ⏱ RTO পুরো! → seg-2 পুনঃপাঠায়
t=0.550  S: seg-2 → ACK-1501
t=0.600  C: base=6 → বাকি সব...

মোট জরিমানা: RTO পুরোটা (৫০০ms) — fast path-এর ~১০ গুণ। এই সংখ্যাটাই নিচের build-এ নিজের চোখে দেখবেন।

ছবিতে

স্বাভাবিক:                     ফাঁক (seg-2 lost):          recovery-র পরে:
bytes: 1 2 3 4 5 6 ...        bytes: 1 [2] 3 4 5 ...       bytes: 1 2 3 4 5 6 ...
       ████████░░░░                  █░█████░░░░                   ██████████
base↑                             base (stuck at 2)                base→
next→                             next→                            next→
rwnd=4                            window ভরা, কিছুই               window স্লাইড,
(৪টা unacked পর্যন্ত)             নতুন পাঠানো যাবে না            আবার চলছে
Sliding window তিন অবস্থায় — ack আসা মাত্র জানালাটা ডানে সরে, নতুন byte পাঠানোর অনুমতি জন্মায়; ফাঁক থাকলে জানালা আটকে থাকে।

আর reliability-র স্তর-মানচিত্র:

একটা হারানো packet ধরা পড়ে যে তিন পর্যবেক্ষণে
  1. Dup-ACK storm~1 RTT-তে signal — সবচেয়ে দ্রুত, fast retransmit
  2. SACK blocksঠিক কোন gap — শুধু সেটাই পুনঃপাঠানো
  3. RTO timeoutশেষ ভরসা — সেকেন্ড-স্কেল, exponential backoff
  4. Connection abortবারবার backoff-এর পরেও নীরব → error app-এ

নিজে চালিয়ে দেখুন

EXPERIMENT

dup-ACK আর RTO নিজের চোখে — capture পড়া ও (sudo হলে) loss ঢোকানো

macOS / Linux· ২০ মিনিট

ধাপ ১ — capture চালু করে ছোট transfer:

# terminal A (sudo লাগে; না থাকলে নিচের pre-made trace পড়ুন)
sudo tcpdump -i any -n -S 'tcp port 8443 and host 127.0.0.1'
# terminal B: ছোট server + transfer
python3 -m http.server 8443 &
curl -s http://127.0.0.1:8443/ >/dev/null

Localhost-এ loss হয় না — তাই এখানে dup-ACK দেখবেন না; দেখবেন in-order ACK-র cumulative এগোনো (ack মান লাফিয়ে লাফিয়ে বাড়ছে)। Loss দেখতে হলে নিচের দুই path।

ধাপ ২ (sudo থাকলে) — netem দিয়ে loss ঢোকান:

# Linux: loopback-এ ২% packet loss + সামান্য delay
sudo tc qdisc add dev lo root netem delay 20ms loss 2%
curl -s -o /dev/null http://127.0.0.1:8443/bigfile
# capture-এ এখন খুঁজুন: একই ack মান ৩ বার, তারপর seq-এ পিছনে গিয়ে
# একটা segment retransmit
sudo tc qdisc del dev lo root    # পরিষ্কার করতে ভুলবেন না!

ধাপ ৩ (sudo ছাড়া) — lesson-provided trace পড়ুন:

নিচের excerpt-টা 2% loss-সহ transfer থেকে — হাতে annotate করুন:

12:00:01.101 IP .5000 > .8443: Flags [.] seq 14401:14801   ← seg-4
12:00:01.102 IP .8443 > .5000: Flags [.] ack 14401          ← seg-3 এর পরে? gap!
12:00:01.104 IP .5000 > .8443: Flags [.] seq 15201:15601   ← seg-5 (out of order)
12:00:01.105 IP .8443 > .5000: Flags [.] ack 14401          ← DUP #2
12:00:01.107 IP .5000 > .8443: Flags [.] seq 15601:16001   ← seg-6
12:00:01.108 IP .8443 > .5000: Flags [.] ack 14401          ← DUP #3!
12:00:01.109 IP .5000 > .8443: Flags [.] seq 14401:14801   ← ★ retransmit (একই seq!)
12:00:01.130 IP .8443 > .5000: Flags [.] ack 16401          ← লাফিয়ে 16401!

পড়ার চাবি: ack 14401 চারবার — প্রথমবার স্বাভাবিক, পরের তিনবার dup; তার ঠিক পরেই একই seq 14401:14801 আবার — fast retransmit। শেষ ACK-র বিশাল লাফ (14401→16401) প্রমাণ — out-of-order segment-গুলো buffer-এ অপেক্ষা করছিল, gap ভরতেই সব একসাথে acknowledge হলো।

এটা কী প্রমাণ করে

capture-এ হারানো segment-এর ঠিক ৩টা duplicate ACK আর তার পরে একটা retransmit দেখা যায় — theory-র ৩-dup নিয়ম wire-এ হুবহু; RTO-path-এ সেই জায়গায় শুধু দীর্ঘ নীরবতা।

নিজে বানান

BUILD IT

ACK-clock simulator — fast retransmit vs RTO, সেকেন্ডে সেকেন্ড

Python · ●●●○○
  1. Discrete-event engine: heapq-তে (time, kind, payload)-র event
  2. Receiver: expected counter + out-of-order buffer + dup-ACK emission
  3. Sender: sliding window, ৩-dup fast retransmit, RTO backoff
  4. একই loss scenario দুই mode-এ চালান — fast ON vs OFF
  5. Timeline print + শেষ সময়ের তুলনা — পার্থক্যটাই লেসন
import heapq

NSEG       = 10      # মোট segment (প্রতিটা 1 MSS ধরে)
WINDOW     = 5       # sender window
ONE_WAY    = 0.050   # 50 ms একমুখী → RTT 100 ms
RTO0       = 0.500   # initial timeout
DROP_FIRST = {2}     # প্রথম পাঠে হারাবে এই segment-গুলো
REORDER    = {4: 0.030}   # extra delay (sec) — reorder demo

def run(fast_retx):
    drop = set(DROP_FIRST)
    events = []                                  # (time, kind, val)
    def tx(seq, now, tag=""):
        if seq in drop:
            drop.discard(seq)
            print(f"{now:7.3f}  seg-{seq} পাঠানো হলো{tag} … ✗ পথে হারাল")
            return
        heapq.heappush(events, (now + ONE_WAY + REORDER.get(seq, 0),
                                "seg", seq))
        print(f"{now:7.3f}  seg-{seq} পাঠানো হলো{tag}")

    print(f"\n=== mode: fast retransmit {'চালু' if fast_retx else 'বন্ধ'} ===")
    expected, ooo = 1, set()                     # receiver side
    base, nxt = 1, 1                             # sender pointers
    dup, rto_val, rto_dl = 0, RTO0, RTO0
    finish = None
    for s in range(1, min(WINDOW, NSEG) + 1):    # initial window fill
        tx(s, 0.0)

    while finish is None:
        head = events[0][0] if events else float("inf")
        if rto_dl is not None and rto_dl <= head:            # RTO আগে?
            print(f"{rto_dl:7.3f}  ⏱ RTO! seg-{base} পুনঃপ্রেরণ")
            tx(base, rto_dl, tag="(timeout)")
            rto_val *= 2                                     # exponential backoff
            rto_dl += rto_val
            continue
        if not events:
            break
        now, kind, val = heapq.heappop(events)
        if kind == "seg":                                    # receiver-এ পৌঁছাল
            if val == expected:
                expected += 1
                while expected in ooo:
                    ooo.discard(expected); expected += 1
                heapq.heappush(events, (now + ONE_WAY, "ack", expected))
                print(f"{now:7.3f}  seg-{val} পৌঁছাল → ACK-{expected}")
            else:
                ooo.add(val)
                heapq.heappush(events, (now + ONE_WAY, "ack", expected))
                print(f"{now:7.3f}  seg-{val} out-of-order "
                      f"(seg-{expected} নেই!) → DUP-ACK-{expected}")
        else:                                                # sender ACK পেল
            if val > base:
                base, dup = val, 0
                rto_val = RTO0; rto_dl = now + rto_val
                while nxt <= NSEG and nxt < base + WINDOW:
                    tx(nxt, now); nxt += 1
                if base > NSEG:
                    finish = now
            else:                                            # duplicate
                dup += 1
                print(f"{now:7.3f}  DUP-ACK-{val} পেল (#{dup})")
                if fast_retx and dup == 3:
                    print(f"{now:7.3f}  ★ FAST RETRANSMIT seg-{base}")
                    tx(base, now, tag="(fast)")
                    dup, rto_dl = 0, now + rto_val
    return finish

t_fast = run(True)
t_slow = run(False)
print("\n=== ফলাফল ===")
print(f"fast retransmit সহ : {t_fast:.3f}s")
print(f"শুধু RTO-তে        : {t_slow:.3f}s")
print(f"জরিমানার পার্থক্য   : +{(t_slow - t_fast)*1000:.0f} ms")

প্রত্যাশিত আকারের ফলাফল:

=== mode: fast retransmit চালু ===
  0.000  seg-1 পাঠানো হলো
  0.000  seg-2 পাঠানো হলো … ✗ পথে হারাল
  ...
  0.085  ★ FAST RETRANSMIT seg-2
  0.330  ACK-11 পেল
=== mode: fast retransmit বন্ধ ===
  0.500  ⏱ RTO! seg-2 পুনঃপ্রেরণ
  ...
  0.850  ACK-11 পেল
=== ফলাফল ===
fast retransmit সহ : 0.330s
শুধু RTO-তে        : 0.850s
জরিমানার পার্থক্য   : +520 ms

নিজে বাড়ান:

  1. REORDER বাড়িয়ে এমন অবস্থা বানান যেখানে ৩-dup নিয়ম ভুল fast retransmit trigger করে (spurious) — দেখুন receiver তবু সঠিক stream বানায়, শুধু bandwidth নষ্ট হয়
  2. SACK block report যোগ করুন — receiver dup-ACK-র সাথে ooo-set ছাপবে; sender শুধু gap পুনঃপাঠাবে
  3. DROP_FIRST-এ পরপর দুই segment যোগ করুন — fast retransmit- এর পরেও দ্বিতীয় loss-এর জন্য আবার ৩-dup জমা দেখুন

বাস্তব সিস্টেমে

বাস্তব জগতে reliability যন্ত্র

Mobile handover-এর spurious storm। Cell tower বদলাতে গিয়ে কয়েকশো ms নীরবতা — sender-দের RTO আগুন ধরে, একসাথে retransmit ঢালে, নতুন cell ভরে যায় — আবার loss — আবার retransmit। Modern stack-দের RTO randomization আর pacing ঠিক এই thundering herd ভাঙতে।

Satellite link-এর long-fat problem। GEO satellite RTT ~600ms; window ছোট হলে throughput = window/RTT — গণিতগতভাবে সীমাবদ্ধ। Window scale option (handshake-এর options-এ) ঠিক এজন্যই — 16-bit field-কে 32-bit পর্যন্ত বাড়ায়।

Datacenter-এর micro-RTO। LAN-এ RTT 100μs; 200ms RTO তাহলে 2000× বেশি! DCTCP আর RACK-TLP (Linux-এর আধুনিক loss recovery) এজন্যই — tail-loss probe দিয়ে RTO-কে প্রায় অপ্রয়োজনীয় করে তোলে।

rsync/scp-এর “stall” গল্প। বড় transfer হঠাৎ সেকেন্ডের পর সেকেন্ড থেমে আবার চলে — সাধারণত RTO backoff-chain (0.5→1→2→4s) আর zero-window probing-এর সম্মিলিত নাটক; ss -ti-এর retrans counter-ই অপরাধীর পরিচয়।

Karn’s algorithm প্রতিদিন। আপনার browser-এর প্রতিটা connection নীরবে এটা মানে — retransmit-এর পরে RTT sample discard, নইলে estimation ঘুরে যেত।

যে ভুলগুলো সবাই করে

“ACK-5000 মানে '5000-তম byte-টা এসেছে' — এতটুকুই জানা গেল।”

Cumulative মানে তার অনেক বেশি: ISN+1 থেকে 5000 পর্যন্ত সব byte পাওয়া গেছে — কোনো ফাঁক নেই। এই একটা সংখ্যা হাজার byte-এর receipt। উল্টো দিকটাও সত্যি: 5001 এবং 5200 পেয়েছি কিন্তু 5100 নেই — তবু ACK বলবে শুধু 5001-এর পর্যন্ত; বাকিটা বলতে হয় SACK option দিয়ে।

“RTO একটা fixed value — সব connection-এ 200ms।”

RTO per-connection, per-moment dynamic — SRTT+4·RTTVAR দিয়ে বারবার হিসাব হয়, loss-এ ২× করে backoff, recovery-র পরে আবার ছোট হয়। Satellite link-এ শুরুতেই সেকেন্ড-স্কেল, datacenter-এ sub-millisecond হওয়ার কথা। Fixed 200ms ধরে কথা বললে পরীক্ষার উত্তর ভুল, production debugging আরও ভুল।

“Retransmission মানে receiver-এ ভুল data পৌঁছাবে — duplicate হয়ে যাবে।”

Receiver-এর sequence-number dedup ঠিক এই জন্যই: দুইবার আসা segment-এর দ্বিতীয়টা নীরবে ফেলে দেয়, application কখনো দেখেই না। Spurious retransmit-এর খরচ bandwidth আর CPU — correctness নয়। Reliability model-টা exactly-once delivery, “at-least-once তারপর dedup” নয়।

“Flow control আর congestion control একই জিনিসের দুই নাম।”

Flow control receiver-এর স্বার্থে — “আমার buffer ছোট, ধীরে পাঠাও” (rwnd)। Congestion control network-এর স্বার্থে — “router-গুলো ভরে যাচ্ছে, ধীরে পাঠাও” (cwnd)। একটা endpoint-এর সাথে কথা বলে, অন্যটা path-এর সাথে। পরের লেসনের প্রথম পাঠটা এই distinction ভাঙাই।

বুঝেছেন কি না দেখুন

1

Cumulative ACK-এ ack = 3000 — এই একটা সংখ্যা ঠিক কী কী দাবি করে?

স্মরণ

“ISN+1 থেকে 2999 পর্যন্ত সব byte পেয়েছি, ফাঁক নেই; পরের expected byte 3000।” একটা সংখ্যা সম্পূর্ণ prefix-এর receipt — 3000-এর পরের কোনো byte পাওয়ার তথ্য এতে নেই।

2

SRTT = 100ms, RTTVAR = 20ms, G উপেক্ষাযোগ্য। নতুন measurement R’ = 180ms এলে (α=⅛, β=¼) নতুন SRTT, RTTVAR আর RTO কত?

প্রয়োগ

SRTT ← (7/8)(100) + (1/8)(180) = 87.5 + 22.5 = 110ms। RTTVAR ← (3/4)(20) + (1/4)|110−180| = 15 + 17.5 = 32.5ms। RTO ← 110 + 4(32.5) = 240ms। লক্ষ্য করুন: একটা খারাপ sample গড়কে সামান্যই নড়ায়, কিন্তু deviation বেড়ে RTO দ্রুত বড় হয় — system সতর্ক হয়ে ওঠে।

3

Fast retransmit কেন ১টা dup-ACK-তে আগুন ধরে না — ঠিক ৩-তে কেন?

যুক্তি

Reordering-এর জন্য জায়গা: packet কাগজে-কলমে হারায়নি, পথে পিছিয়ে আছে — তখনও ১–২টা dup-ACK আসতে পারে। ৩টা dup-ACK মানে অন্তত তিনটা পরবর্তী segment সফলভাবে পৌঁছেছে — ওই একটার এত পিছিয়ে থাকা প্রায় নিশ্চিতভাবে loss, না হলে অতি-ব্যতিক্রমী reorder। Trade-off: কম threshold = বেশি spurious retransmit, বেশি = দেরি। ৩ হলো ঐতিহাসিক sweet spot।

4

RTT = 100ms, RTO হিসাব হয়েছে 250ms। Timeout টানা ৩বার লাগলে চতুর্থ attempt-টা কত ms অপেক্ষা করবে?

প্রয়োগ

Exponential backoff: 250 → 500 → 1000 → 2000ms। প্রতিটা ব্যর্থ retransmission-এ দ্বিগুণ — network-কে সুস্থ হওয়ার নিঃশ্বাস দেওয়া। (Maximum-এর সীমা typical 64s; তার আগে না ফিরলে connection abort।)

5

Receiver-এর application কোনো data পড়ছে না (busy loop-এ আটকে)। TCP-র কোন যন্ত্র sender-কে থামাবে? আর সেই থামা অবস্থায় deadlock কীভাবে এড়ানো হয়?

যুক্তি

Receive buffer ভরে গেলে receiver window=0 advertise করে — sender-এর effective window শূন্য, কিছুই পাঠাতে পারে না (flow control কাজ করছে)। Deadlock-ঝুঁকি: window খোলার সংবাদ বহনকারী packet হারালে দুই পক্ষই অপেক্ষায় — এড়ায় persist timer: sender মাঝে মাঝে 1-byte window probe পাঠায়, receiver-এর প্রতিটা উত্তরে বর্তমান window-ও আসে।

6

আপনার protocol-এ একটা নতুন idea: “প্রতিটা packet-এর নিজস্ব timer, যেটা প্রথমেই expire হবে শুধু সেটাই retransmit হবে” (selective repeat-এর pure রূপ)। TCP কেন এটা করে না — state আর wire-এর কোন খরচে?

ডিজাইন

Per-segment timer মানে in-flight প্রতিটা segment-এর জন্য আলাদা timer-state — kernel-এ লক্ষ connection × শত শত segment = বিশাল memory/compute। TCP-র পছন্দ: একটা timer (base-এর উপর) + dup-ACK heuristic — প্রায় সব case-এ যথেষ্ট, cost O(1)। SACK যোগ হওয়ায় selective behavior পাওয়া গেছে wire-এ সামান্য option-byte খরচে, timer-এর state ছাড়াই — engineering-এর classic “state-কে wire-এ দাবি করো” move।

এরপর কী

এরপর কী

আমরা দেখলাম TCP কীভাবে নিজের ভুল শোধরায়। কিন্তু একটা গভীর প্রশ্ন ঝুলে আছে: sender কি শুধু receiver-কেই খেয়াল করে? Path-এর router-গুলো ভরে গেলে কে বলবে?

পরের লেসন — Congestion Control:

  • Flow control (receiver) বনাম congestion control (network) — min(rwnd, cwnd)-এর দুই জগৎ
  • Slow start-এর বিস্ফোরক নাম-বিপরীত বৃদ্ধি, AIMD-র sawtooth, আর fairness-এর যুক্তি
  • Reno → CUBIC → BBR: তিন প্রজন্মের দর্শন
  • Bufferbloat আর ১৯৮৬-এর internet-collapse গল্পটা, যেটা সব শুরু করেছিল

আজকের simulator-এ window ছিল fixed — পরের লেসনে সেটাই জীবন্ত হবে।

আরও পড়ুন

  • RFC 9293 — Transmission Control Protocol (updated spec) — W. Eddy (ed.), IETF · Sequence/acknowledgment semantics-এর authoritative source
  • RFC 6298 — Computing TCP's Retransmission Timer — V. Paxson, M. Allman et al. · SRTT/RTTVAR/RTO সূত্রের মূল document — এই লেসনের গণিতের ভিত্তি
  • RFC 2018 — TCP Selective Acknowledgment Options — M. Mathis, J. Mahdavi et al. · SACK option-এর specification — gap reporting কীভাবে কাজ করে