Thuta Learning
Computer Networking
IntermediateDevOps & Toolsbeginner

TCP Reliability: ACK, Retransmission နဲ့ Window

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • TCP Reliability: ACK, Retransmission နဲ့ Window concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး packet/data ဘယ်လိုသွားလာသလဲ ခြေရာခံနိုင်ရန်
  • နမူနာ code ကို ကိုယ်တိုင် run ပြီး output စစ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

TCP ရဲ့ sequence number တွေဟာ packet တွေကို မရေတွက်ဘဲ byte တွေကို ရေတွက်တယ် ဆိုတာ အရင်ဆုံး နားလည်ဖို့ လိုတယ်။ segment တစ်ခုရဲ့ sequence number က သူပါလာတဲ့ ပထမဆုံး byte ရဲ့ stream ထဲက နေရာကို ပြောပြတာ ဖြစ်တယ်။ ဒါကြောင့် TCP ဟာ segment တွေကို ပြန်ခွဲ၊ ပြန်ပေါင်းလို့ ရတယ် — retransmit လုပ်တဲ့အခါ မူလ segment နဲ့ အတိအကျ တူစရာ မလိုဘူး။ ACK ကလည်း cumulative ဖြစ်တယ်၊ ဆိုလိုတာက 'ငါ မျှော်နေတဲ့ နောက်ထပ် byte' ကို ပြောတာ ဖြစ်ပြီး၊ အလယ်မှာ အပေါက်တစ်ခု ရှိနေရင် နောက်က ဘယ်လောက် ရောက်ရောက် ACK က ရှေ့မတိုးဘဲ ရပ်နေတယ်။ ဒါက ရိုးရှင်းပြီး ခိုင်မာပေမယ့် သတင်းအချက်အလက် နည်းသွားတယ် — အပေါက်နောက်က ဘာတွေ ရောက်ပြီးပြီလဲ ဆိုတာကို ပြောပြဖို့ SACK option ကို နောက်မှ ထပ်ဖြည့်ခဲ့ရတာ ဖြစ်တယ်။

ACK မပြန်လာရင် ဘာဖြစ်မလဲ။ sender က segment တစ်ခုချင်းစီ အတွက် retransmission timer တစ်ခု ထားပြီး၊ timer ကုန်သွားရင် ပြန်ပို့တယ်။ ဒီ timeout ဟာ ကိန်းသေ မဟုတ်ဘဲ ကိုယ်တိုင် တိုင်းထားတဲ့ round-trip time ရဲ့ ပျမ်းမျှနဲ့ ကွဲလွဲမှုကနေ တွက်ထားတာ ဖြစ်တယ်။ RTO ကို စောင့်ရတာ နှေးလွန်းလို့ TCP က duplicate ACK သုံးခု ဆက်တိုက် ရောက်လာရင် timer မကုန်ခင် ချက်ချင်း ပြန်ပို့တဲ့ fast retransmit ကို လုပ်တယ်။

တစ်ခုပို့ တစ်ခုစောင့် လုပ်နေရင် RTT တစ်ခေါက်လျှင် segment တစ်ခုပဲ ရမှာ ဖြစ်လို့ sliding window လိုအပ်လာတယ် — ACK မရသေးဘဲ အပြိုင် ပို့ခွင့်ရှိတဲ့ byte အရေအတွက် ဖြစ်တယ်။ ဒီနေရာမှာ ရောထွေးလွယ်တဲ့ အချက်တစ်ခု ရှိတယ်။ window နှစ်မျိုး ရှိတယ် — receive window (rwnd) ကို receiver က segment တိုင်းမှာ ကြေညာပြီး 'ငါ့ buffer ဒီလောက်ပဲ ကျန်တယ်' လို့ ပြောတာ ဖြစ်တယ်၊ ဒါက flow control။ congestion window (cwnd) ကတော့ sender ကိုယ်တိုင် ခန့်မှန်းထားတဲ့ ကိန်းဂဏန်း ဖြစ်ပြီး wire ပေါ်မှာ ဘယ်ဘယ်လောက် တစ်ပြိုင်နက် ရှိလို့ ရမလဲ ဆိုတာကို packet ပျောက်မှုကနေ သင်ယူပြီး ချိန်ညှိတာ ဖြစ်တယ်၊ ဒါက congestion control။ အမှန်တကယ် ပို့နိုင်တာက ဒီနှစ်ခုရဲ့ အနည်းဆုံးတန်ဖိုး ဖြစ်တယ်။ receiver က မလိုက်နိုင်တာနဲ့ network က မလိုက်နိုင်တာ လုံးဝ မတူတဲ့ ပြဿနာ နှစ်ခု ဖြစ်လို့ TCP က နှစ်ခုကို သီးခြားစီ ကိုင်တွယ်ထားတာ ဖြစ်တယ်။

text
SENDER'S SLIDING WINDOW OVER THE BYTE STREAM
--------------------------------------------
   +----+----+----+----+----+----+----+----+----+----+----+----+
   | 00 | 04 | 08 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 |
   +----+----+----+----+----+----+----+----+----+----+----+----+
   |<--- ACKed -->|<-- sent, not ACKed --->|<-- cannot send -->|
                  ^                        ^
            send_base                 send_base + rwnd

ACK for 1024 arrives -> send_base moves right, the window slides
ACK for 1012 repeats -> hole at 1012, window frozen until the
                        retransmission fills it

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

Singapore က server တစ်ခုကနေ Yangon ကို 1 GB backup file တစ်ခု ကူးနေတယ် ဆိုပါစို့။ link နှစ်ဖက်စလုံးက 1 Gbps ဖြစ်ပေမယ့် transfer က 6 Mbps လောက်ပဲ ရနေတယ်။ bandwidth ပြဿနာ မဟုတ်ဘူး၊ window ပြဿနာ ဖြစ်တယ်။ RTT က 80 ms ဖြစ်ပြီး receive window က 64 KB (window scaling မပါတဲ့ TCP ရဲ့ အများဆုံး) ဆိုရင် — RTT တစ်ခေါက်လျှင် 64 KB ပဲ ပို့လို့ရလို့ throughput အများဆုံးဟာ 64 KB / 0.08 s = 800 KB/s လောက်သာ ဖြစ်တယ်။ bandwidth ဘယ်လောက် ရှိရှိ ဒီထက် မတက်နိုင်ဘူး။ ဒီကိန်းကို bandwidth-delay product လို့ ခေါ်ပြီး၊ ဖြေရှင်းနည်းက window scaling option ကို ဖွင့်ပေးဖို့ (မျက်မှောက်ခေတ် OS တွေမှာ default ဖွင့်ပြီးသား ဖြစ်ပေမယ့် middlebox ဟောင်း တစ်ချို့က ဖြတ်ပစ်တတ်တယ်) ဖြစ်တယ်။

ဆန့်ကျင်ဘက် အခြေအနေကို လည်း သတိထားပါ။ ACK တွေ တက်လာနေပေမယ့် throughput က မကြာခဏ ကျသွားပြီး packet loss နည်းနည်း တွေ့ရင် — ဒါက receiver မလိုက်နိုင်တာ မဟုတ်ဘဲ congestion control အလုပ်လုပ်နေတာ ဖြစ်တယ်။ loss တွေ့တိုင်း cwnd ကို လျှော့ချ၊ ပြန်တိုးလို့ sawtooth ပုံစံ ဖြစ်လာတယ်။ ဒီနှစ်ခုကို ခွဲခြားနည်းက ရိုးရှင်းတယ် — rwnd က သေးနေရင် ပြဿနာက အဆုံးထိပ်နှစ်ဖက်မှာ၊ loss ရှိပြီး rwnd ကြီးနေရင် ပြဿနာက ကြားထဲက network မှာ ဖြစ်တယ်။

အတူတူ စမ်းရေးကြည့်မယ်

python
ISN = 1000      # sequence number of the first byte of the stream
MSS = 4         # bytes carried per segment
RWND = 12       # receiver's advertised window, in bytes
TOTAL = 24      # bytes the application handed to TCP

# The network drops segment 3 the first time it is transmitted.
DROP_FIRST_TRY = {3}

send_base = ISN     # oldest byte sent but not yet acknowledged
next_seq = ISN      # next byte we are allowed to put on the wire
received = set()    # segment numbers the receiver actually holds
already_dropped = set()
on_wire = 0


def cumulative_ack(received_segs):
    """The ACK number is the next byte expected IN ORDER, so a hole
    in the middle freezes it no matter what arrives after the hole."""
    n = 0
    while n in received_segs:
        n += 1
    return ISN + n * MSS


print("seg   seq  event                      ACK  base  usable")
print("---  ----  -------------------------  ----  ----  ------")

while send_base < ISN + TOTAL:
    progressed = False

    # 1. Fill the window: send every segment the window still allows.
    while next_seq < send_base + RWND and next_seq < ISN + TOTAL:
        seg = (next_seq - ISN) // MSS
        if seg in DROP_FIRST_TRY and seg not in already_dropped:
            already_dropped.add(seg)
            event = "sent -> DROPPED en route"
        else:
            received.add(seg)
            event = "sent -> arrives"
        next_seq += MSS
        on_wire += 1
        ack = cumulative_ack(received)
        print("%3d  %4d  %-25s  %4d  %4d  %6d"
              % (seg, next_seq - MSS, event, ack, send_base,
                 send_base + RWND - next_seq))

    # 2. Slide the window as far as the cumulative ACK allows.
    ack = cumulative_ack(received)
    if ack > send_base:
        send_base = ack
        progressed = True

    # 3. Window full and the ACK never moved -> the RTO fires and we
    #    retransmit only the segment sitting at send_base.
    if not progressed:
        seg = (send_base - ISN) // MSS
        received.add(seg)
        on_wire += 1
        ack = cumulative_ack(received)
        print("%3d  %4d  %-25s  %4d  %4d  %6d"
              % (seg, send_base, "RETRANSMIT after timeout", ack,
                 send_base, send_base + RWND - next_seq))
        send_base = ack

print()
print("bytes delivered in order :", cumulative_ack(received) - ISN)
print("segments put on the wire :", on_wire, "for", TOTAL // MSS, "segments")
You should see
seg   seq  event                      ACK  base  usable
---  ----  -------------------------  ----  ----  ------
  0  1000  sent -> arrives            1004  1000       8
  1  1004  sent -> arrives            1008  1000       4
  2  1008  sent -> arrives            1012  1000       0
  3  1012  sent -> DROPPED en route   1012  1012       8
  4  1016  sent -> arrives            1012  1012       4
  5  1020  sent -> arrives            1012  1012       0
  3  1012  RETRANSMIT after timeout   1024  1012       0

bytes delivered in order : 24
segments put on the wire : 7 for 6 segments

၅ မိနစ် စမ်းကြည့်

RWND ကို 8 ကနေ 24 အထိ ပြောင်းပြီး run ကြည့်ပါ — wire ပေါ် ရောက်တဲ့ segment အရေအတွက် ဘယ်လို ပြောင်းသလဲ။ ပြီးရင် DROP_FIRST_TRY ကို {1, 4} လုပ်ပြီး cumulative ACK ဟာ အပေါက်နှစ်ခုရှိရင် ဘယ်လို ပြုမူသလဲ ကြည့်ပါ။

သတိလေးတစ်ချက်

throughput နှေးတိုင်း bandwidth နည်းလို့ လို့ ကောက်ချက်ချတာ။ RTT မြင့်တဲ့ link ပေါ်မှာ window သေးတာက bandwidth ဘယ်လောက်ရှိရှိ ခေါင်းစဉ်တပ် ကန့်သတ်ပစ်တယ်။

flow control နဲ့ congestion control ကို တစ်ခုတည်း လို့ ထင်တာ။ rwnd ကို tune လုပ်ခြင်းက congestion ကို မဖြေရှင်းသလို congestion algorithm ပြောင်းခြင်းကလည်း receiver buffer သေးတာကို မကူညီပါဘူး။

RFC 5681 - TCP Congestion ControlComputer Networking

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • throughput နှေးတိုင်း bandwidth နည်းလို့ လို့ ကောက်ချက်ချတာ။ RTT မြင့်တဲ့ link ပေါ်မှာ window သေးတာက bandwidth ဘယ်လောက်ရှိရှိ ခေါင်းစဉ်တပ် ကန့်သတ်ပစ်တယ်။
  • flow control နဲ့ congestion control ကို တစ်ခုတည်း လို့ ထင်တာ။ rwnd ကို tune လုပ်ခြင်းက congestion ကို မဖြေရှင်းသလို congestion algorithm ပြောင်းခြင်းကလည်း receiver buffer သေးတာကို မကူညီပါဘူး။
  • နမူနာ code ကို production network ပေါ် တိုက်ရိုက်မစမ်းဘဲ local/test environment တွင် အရင်အတည်ပြုပါ။

လေ့ကျင့်ခန်း

RWND ကို 8 ကနေ 24 အထိ ပြောင်းပြီး run ကြည့်ပါ — wire ပေါ် ရောက်တဲ့ segment အရေအတွက် ဘယ်လို ပြောင်းသလဲ။ ပြီးရင် DROP_FIRST_TRY ကို {1, 4} လုပ်ပြီး cumulative ACK ဟာ အပေါက်နှစ်ခုရှိရင် ဘယ်လို ပြုမူသလဲ ကြည့်ပါ။

You'll know it worked when: seg seq event ACK base usable --- ---- ------------------------- ---- ---- ------ 0 1000 sent -> arrives 1004 1000 8 1 1004 sent -> arrives 1008 1000 4 2 1008 sent -> arrives 1012 1000 0 3 1012 sent -> DROPPED en route 1012 1012 8 4 1016 sent -> arrives 1012 1012 4 5 1020 sent -> arrives 1012 1012 0 3 1012 RETRANSMIT after timeout 1024 1012 0 bytes delivered in order : 24 segments put on the wire : 7 for 6 segments

TCP Reliability: ACK, Retransmission နဲ့ Window | Thuta Learning