一覧NW-11-2 · TCPの信頼性と流量

TCPのフロー制御

受信窓の変化から、送信を止める条件と再開の範囲を確認します。

説明

TCPの受信側は、受け取れるデータ量をwindowで知らせます。アプリケーションが受信データを読み出さずバッファが埋まると、送信側は通常の新規データ送信を止める必要があります。

容量3000バイトの受信バッファと通信記録を照合し、ACK・window・送信済み番号から追加送信できる範囲を計算します。空きの回復とゼロウィンドウプローブ、別接続のWindow Scaleも確認します。

1windowが閉じて再び開く通信

window.pcapは接続途中の記録です。この接続ではWindow Scaleを使用しておらず、表示されたwindow値をバイト数として扱います。

tcpdump -n -tt -SACKとwindowの推移 -r window.pcap受信制限の記録

受信側のwindowは2000→1000→0となり、後に3000へ戻ります。0の時点でACKは4001で、3000へ開き直した時点でもACKは4001です。新しいデータを受け取らなくても、空きの回復だけを通知できます。

2受信バッファとアプリケーションの読出し

buffer.csvは容量3000バイトの受信バッファを整理した記録です。

cat buffer.csv使用量・空き・次の受信番号

データが届くと使用量が増え、アプリケーションが読まない間に空きが0になります。アプリケーションが3000バイト読み出すと空きが戻ります。受信側の処理が遅い場合の流量制御であり、必ずしもネットワークの混雑を意味しません。

3許される番号範囲の右端

受信側のACKにwindowを加え、送信できる範囲の排他的な右端を求めます。

python3 -c広告された受信範囲 'import json; rows=json.load(open("window.json")); print([(r["ack"],r["window"],r["ack"]+r["window"]) for r in rows if r["direction"]=="S>C"])'ACKとwindowの和

最初の三行は右端4001で一定です。データを受け取るとACKが進み、その分windowが減ります。空きを回復した通知では右端7001まで広がり、再びデータを送れるようになります。

4未確認の送信済みデータを差し引く

ACK2001・window2000のとき、送信側が既に3001まで送っている場合を計算します。

python3 -c既送信分を引いた余裕 'ack=2001; window=2000; snd_nxt=3001; print(ack+window-snd_nxt); print(list(range(snd_nxt,ack+window))[:3],ack+window-1)'追加できる範囲

新たに送れるのは1000バイトで、番号3001から4000です。window2000をそのまま追加送信量と読むと、既に飛行中の1000バイトを二重に数えてしまいます。実際には輻輳ウィンドウによる制限も同時に受けます。

5windowが0のままに見える場合

空きを通知したACKだけが失われると、送信側は受信側の回復を知りません。ゼロウィンドウと回復後を同じ番号で比較します。

python3 -cゼロと回復後の送信余裕 'ack=4001; snd_nxt=4001; print([(w,max(0,ack+w-snd_nxt)) for w in (0,3000)])'同じACKでwindowだけ変更

0の間は通常の新規データを進められず、回復後は3000バイト分の余裕があります。送信側はゼロウィンドウプローブで現在の受信窓を確認する仕組みを持ちます。プローブは通常のデータを大量送信してよいという意味ではありません。

6Window Scaleを使う別の接続

別の接続で、受信側がSYN時にshift count7を広告したとします。確立後のwindow欄500を実際のバイト数へ変換します。

python3 -c欄の値をスケールする 'field=500; shift=7; print(field*(2**shift)); print(65535*(2**shift))'shift count7の倍率

500は64000バイト、欄の最大65535は8388480バイトを表せます。倍率は方向ごとにSYNで交渉し、SYNそのもののwindow欄には適用しません。途中の記録だけでは倍率が分からない場合があります。

まとめ

ACKとwindowで受信範囲を求め、送信済みのデータを差し引きます。

受信側の空きによる制限と、ネットワークの混雑による制限は別です。

操作履歴と解説を振り返れます。

シェル / PCAPと通信の記録

1
tcpdump -n -tt -S -r window.pcap
reading from file window.pcap, link-type EN10MB (Ethernet)
0.000000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 2001, win 2000, length 0
0.010000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 2001:3001, ack 9001, win 8192, length 1000: HTTP
0.020000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 3001, win 1000, length 0
0.030000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 3001:4001, ack 9001, win 8192, length 1000: HTTP
0.040000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 4001, win 0, length 0
0.050000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 4001, win 3000, length 0
0.060000 IP 192.0.2.10.49152 > 198.51.100.20.8080: Flags [P.], seq 4001:5001, ack 9001, win 8192, length 1000: HTTP
0.070000 IP 198.51.100.20.8080 > 192.0.2.10.49152: Flags [.], ack 5001, win 2000, length 0
2
cat buffer.csv
EVENT,RCV_NXT,BUFFER_USED,BUFFER_FREE
initial,2001,1000,2000
receive_1000,3001,2000,1000
receive_1000,4001,3000,0
application_reads_3000,4001,0,3000
receive_1000,5001,1000,2000
3
python3 -c 'import json; rows=json.load(open("window.json")); print([(r["ack"],r["window"],r["ack"]+r["window"]) for r in rows if r["direction"]=="S>C"])'
[(2001, 2000, 4001), (3001, 1000, 4001), (4001, 0, 4001), (4001, 3000, 7001), (5001, 2000, 7001)]
4
python3 -c 'ack=2001; window=2000; snd_nxt=3001; print(ack+window-snd_nxt); print(list(range(snd_nxt,ack+window))[:3],ack+window-1)'
1000
[3001, 3002, 3003] 4000
5
python3 -c 'ack=4001; snd_nxt=4001; print([(w,max(0,ack+w-snd_nxt)) for w in (0,3000)])'
[(0, 0), (3000, 3000)]
6
python3 -c 'field=500; shift=7; print(field*(2**shift)); print(65535*(2**shift))'
64000
8388480

演習完了