プロンプトと適用範囲
送信側がシーケンス番号1,000から始まる5つの連続した1,000バイトのTCPデータセグメントを送信します。2番目のセグメントが失われ、後続の3つは受信側に到達します。ACK、SACK、および再送の順序を導出し、タイムアウト、高速再送、および現代的な損失検出の境界を比較してください。
接続は確立されており、5つのセグメントすべてがデータを運んでおり、受信ウィンドウと輻輳ウィンドウは5つすべてがflight状態にあることを許可しており、受信側は順不同(out-of-order)データをバッファリングでき、ハンドシェイク中にSACKがネゴシエートされたと仮定します。シーケンス番号はバイト数をカウントし、以下の範囲は半開区間です。SACKがない場合でも、累積ACKと古典的な高速再送の論理は有効です。送信側は、どのより大きいシーケンス番号のバイトが到着したかに関する情報を失うだけです。
この質問は、バックエンド、インフラストラクチャ、SRE、クライアント、ネットワーキング、および一般的なソフトウェアエンジニアリングの職種に適しています。その核となる能力はトランスポートプロトコルの状態の導出と検証であるため、カテゴリは general です。HTTP/3のストリーム間分離や、TCPの確立、切断、TIME_WAIT については問いません。
面接官が評価するポイント
第一に、候補者が単位を正しく述べられるか。TCPはバイトに番号を付けます。セグメントのシーケンス番号はその最初のデータバイトを識別し、累積ACKは受信側が次に期待するバイトを指定します。パケットカウンタではありません。
第二に、候補者がタイムラインを導出できるか。2番目のセグメントが欠落すると、その後の順不同セグメントは累積ACKを進めませんが、重複ACK(duplicate ACK)を発生させます。3回目の重複ACKは、古典的なRFC 5681の高速再送シグナルです。セグメントAを最初に確認応答するACKを重複としてカウントすると、1つズレるエラー(off-by-one)の回答になります。
第三に、候補者が回復メカニズムを区別して説明できるか。RTOは十分なACKフィードバックを生成しない損失をカバーします。高速再送は、後続のデータによって生成された重複ACKを使用して、より早く回復します。SACKは受信した不連続なバイトブロックを記述し、送信側が配信済みデータの再送を回避できるようにしますが、累積ACKを置き換えるものでも、輻輳ウィンドウを独立して制御するものでもありません。
第四に、候補者が不確実性を説明できるか。パケットの並べ替え(reordering)や複製によっても重複ACKが生成される可能性があり、RTTの急増によって実際の損失なしにタイムアウトが発生する可能性があります。信頼性のある配信、損失検出、および輻輳制御は相互に作用しますが、これらは異なる概念です。
第五に、回答が教科書的なスローガンを超えているか。優れた候補者は、小さなflightや末尾の損失(tail loss)では3つの重複ACKが生成されない可能性がある理由を説明し、送信時刻とSACKフィードバックを組み合わせることで、固定のパケット数閾値やRTOへの依存を減らすために実装でRACK-TLPを使用できることに言及します。
明確化のための質問
- シーケンス番号と長さはバイト単位で表されていますか? まず相対的なパケットラベルをバイト範囲に変換します。SYNとFINもシーケンス空間を消費しますが、この問題には確立された接続上のデータのみが含まれます。
- ハンドシェイク中にSACKはネゴシエートされましたか? SACKがない場合、回復は累積ACKと送信側が選択したアルゴリズムに依存します。SACKがある場合、重複ACKは受信した順不同ブロックを運ぶことができます。
- flightは3つの重複ACKを生成するのに十分な大きさですか? 古典的なシグナルを提供するには、ギャップの後に少なくとも3つの新しいセグメントが到着する必要があります。失われた末尾セグメントには通常、それらのACKを作成するための後続データがありません。
- RFC 5681を説明していますか、それとも特定のカーネルを説明していますか? 古典的な閾値は導出に役立ちます。実際のスタックでは、設定可能な閾値、SACK回復、RACK-TLP、またはその他の拡張機能が使用される場合があるため、パケットトレースの結論にはOSとバージョンが必要です。
- 目標はプロトコルの正確性ですか、それともパフォーマンスの診断ですか? 正確性は最終的な順序どおりの配信を説明します。診断には、RTT、RTO、輻輳状態、並べ替え、キャプチャポイント、およびNICオフロードの動作も必要です。
30秒の回答フレームワーク
「TCPシーケンス番号はバイトをカウントし、累積ACKは次に期待されるバイトです。セグメントAは1,000から1,999をカバーするため、これを受信するとACK 2,000が生成されます。2,000から2,999をカバーするセグメントBは失われます。次の3つのセグメントはバッファリングできますが、ギャップは依然として2,000から始まるため、それぞれが別のACK 2,000を生成します。SACKがネゴシエートされていた場合、それらのACKは3,000から5,999までの受信バイトも報告します。
古典的なRFC 5681のパスでは、3回目の重複ACKによってRTOを待たずにバイト2,000から2,999の高速再送がトリガーされます。ギャップが埋まると、累積ACKは直接6,000に進みます。flightが小さすぎる場合、末尾が失われた場合、またはACKフィードバックが停止した場合、送信側はRTOを必要とする可能性があります。RTOは平滑化されたRTTとRTT変動から導出され、タイムアウト後に指数バックオフが適用され、輻輳ウィンドウが大幅に削減されます。SACKは複数のギャップに対する回復の精度を向上させます。最新のRACK-TLPは、送信時刻、SACKフィードバック、およびプローブを使用して、末尾または再送の損失をより早く検出することもできます。トレースでは、パケットの並べ替えやアナライザのラベルを損失の証拠として扱うのではなく、累積ACK、SACKブロック、再送のタイミング、および実際のTCPスタックを関連付けて分析します。」
ステップバイステップの詳細解説
ステップ1:バイト範囲と累積ACKを確定する
5つの論理バイト範囲は以下のとおりです。
A: SEQ=1000, LEN=1000 -> [1000, 2000) delivered
B: SEQ=2000, LEN=1000 -> [2000, 3000) lost
C: SEQ=3000, LEN=1000 -> [3000, 4000) delivered
D: SEQ=4000, LEN=1000 -> [4000, 5000) delivered
E: SEQ=5000, LEN=1000 -> [5000, 6000) deliveredAが到着した後、受信側はバイト1,000から1,999を連続して保持するため、ACK=2000 を送信します。このACKは確認応答の境界を初めて進めます。これは重複ではなく、新しいACKです。
Bが失われた後、Cは受信ウィンドウ内で受け入れ可能ですが、2,000から始まるギャップを埋めることはできません。受信側はCをバッファリングできますが、累積確認応答は ACK=2000 のままです。同じことがDとEにも当てはまります。RFC 9293では、ACKフィールドを受信側ピアが期待する次のシーケンス番号として定義しています。これが、順不同セグメントの末尾にジャンプしない理由です。
ステップ2:3つの重複ACKと高速再送を導出する
古典的なRFC 5681のパスでは、C、D、およびEの到着ごとに、即座に重複する ACK=2000 が発生します。
Receive A -> ACK 2000 new ACK
Receive C; B is absent -> ACK 2000 + SACK [3000,4000) duplicate ACK 1
Receive D; B is absent -> ACK 2000 + SACK [3000,5000) duplicate ACK 2
Receive E; B is absent -> ACK 2000 + SACK [3000,6000) duplicate ACK 3
Sender retransmits B -> SEQ 2000, LEN 1000
Receiver gets B -> ACK 6000 gap closes3回目の重複ACKが送信側に到達すると、送信側はBがおそらく失われたと推論し、再送タイマーを待たずに [2000,3000) を高速再送します。C、D、Eはすでにバッファリングされています。Bが到着すると、連続する範囲が即座にバイト5,999まで拡張されるため、累積ACKは2,000から6,000へ直接移動できます。
3つの重複ACKは損失のヒューリスティックであり、数学的な証明ではありません。ネットワークの並べ替えによってシーケンス番号の大きいデータが先に配信され、同じACKパターンが生成される可能性があります。複製されたデータセグメントやACKによっても作成される可能性があります。この閾値は、より高速な修復と並べ替えの誤分類との間のトレードオフであり、実際のスタックには他のアルゴリズムが追加されている場合があります。
ステップ3:SACKが追加するものと追加しないものを明確にする
累積ACKは、2,000未満のすべてが連続して到着したことのみを示します。SACKオプションは、[3000,6000) のように、受信バッファに保持されている不連続なブロックを追加で報告できます。これにより情報のギャップが埋まります。送信側はシーケンス番号の大きいデータが到着したことを認識し、複数の穴を修復する際にそれらのブロックをスキップできます。
SACKには3つの重要な境界があります。
- 受信側が以降のACKでSACKブロックを運ぶことができるようになる前に、SYN交換でSACK-Permittedがネゴシエートされている必要があります。
- SACKブロックは、累積ACKを2,000から6,000に進めることはありません。
[2000,3000)を埋めることによってのみ、連続した境界が進みます。 - SACKは受信側からの参考情報(アドバイザリ)です。送信側が回復スコアボードを維持するのに役立ちますが、送信側のアルゴリズムが再送順序と輻輳応答を選択します。
BとDの両方が失われた場合、1回の高速再送では最初はBのみが修復されます。SACKのない古典的な回復では、Dが到着したかどうかについての情報が少なくなります。SACKは別々のCブロックとEブロックを報告できるため、送信側は両方のギャップをより正確に特定し、バッファリングされたCとEの再送を回避できます。
ステップ4:RTOが引き続き必要である理由を説明する
高速再送はACKフィードバックに依存します。送信側がAとBのみを送信し、末尾のセグメントBを失った場合、重複ACKを作成するためのC、D、Eは到着しません。逆方向のACKパスも失敗した場合、送信側は同様に3つの重複を収集できません。再送タイマーは最後のセーフティネットです。
RFC 6298は、平滑化された往復時間 SRTT、往復変動 RTTVAR、およびクロック粒度 G を維持します。
For the first RTT sample R:
SRTT = R
RTTVAR = R / 2
RTO = SRTT + max(G, 4 * RTTVAR)
For a later sample R':
RTTVAR = (1 - 1/4) * RTTVAR + 1/4 * abs(SRTT - R')
SRTT = (1 - 1/8) * SRTT + 1/8 * R'
RTO = SRTT + max(G, 4 * RTTVAR)たとえば、最初のサンプルが R=120 ms であり、G が 240 ms 以下である場合、SRTT=120 ms、RTTVAR=60 ms となり、生の計算式では 360 ms が得られます。RFC 6298では、1秒未満のRTOを1秒に切り上げることを推奨しており、RTTサンプルが存在する前の初期RTOとして1秒を推奨しています。実際のカーネルではより新しいアルゴリズムや実装の詳細が使用される場合があるため、間隔が正確に1秒でないこと自体がタイムアウト回復を否定するものではありません。
タイマーが切れると、送信側は最も古い未確認応答データを再送し、タイマーを再起動する前にRTOを2倍にします。指数バックオフにより、継続的に輻輳しているパスや切断されたパスにデータを繰り返し注入することを防ぎます。再送されたセグメントからRTTを直接測定すると、あいまいさが生じます。ACKは元のセグメントと再送セグメントのどちらをカバーしたものか?そのあいまいさを解決するタイムスタンプがない場合、KarnのアルゴリズムはそのサンプルをRTT更新から除外します。
ステップ5:信頼性の回復と輻輳応答を分離する
再送は「欠落したバイトをどのように置き換えるか?」に答えます。輻輳制御は「その後、どれだけのデータをflight状態にしておくことができるか?」に答えます。損失シグナルは両方に影響を与えますが、2つの役割は異なります。
古典的なRFC 5681は、RTOをより強いシグナルとして扱います。ssthresh は max(FlightSize/2, 2*SMSS) 以下になり、スロースタートが再開される前に cwnd はフルサイズセグメント1つ分以下に低下します。3つの重複ACKは、後続のセグメントが依然として到着しており、ACKクロックが維持されていることを示すため、送信側は高速再送と高速回復に入り、同じRTOリセットを適用せずにウィンドウを削減します。
フロー制御はもう1つの独立した制限です。受信側が通知する rwnd は受信バッファ容量を保護し、送信側の cwnd はネットワークを保護します。どちらも実際の送信を制限します。SACKは受信状態を記述するものであり、rwnd も cwnd も拡大しません。
ステップ6:現代的なRACK-TLPの境界を追加する
固定された3重複ACKルールは、短いflight、末尾の損失、失われた再送、および大幅な並べ替えに対してパフォーマンスが低下します。RFC 8985は、従来の重複ACKカウントの代替としてRACK-TLPを推奨しています。RACKは、各セグメントの最新の送信時刻、RTT、およびSACKフィードバックを組み合わせて、以前の送信が失われたかどうかを推論します。TLPは、末尾付近でACKフィードバックが少ない場合にプローブを送信し、RTOの前にACKクロックの復元を試みます。
面接の回答では、まず古典的なメカニズムを導出し、次に実装の境界を述べる必要があります。実際のトレースでは、3つ未満の重複ACKまたは末尾プローブによる回復が示される場合があります。スタックのアルゴリズムとバージョンを確認してください。すべての実装が「正確に3つの重複を待つか、常にRTOを待つ」と言うのは、教育用モデルを現在の完全な動作と混同しています。
ステップ7:再送カウントではなく、パケットの証拠で検証する
複数の観察間で照合できるタイムラインを構築します。
- 単一の観測ポイントに依存するのではなく、送信側と受信側でキャプチャして、元のセグメントがどこで消失したかを特定します。
SEQ、LEN、累積ACK、およびSACKブロックを揃えて、バイトギャップが存在することを証明します。- 直前の新しいACKの後にのみ重複をカウントします。ベースラインACK自体はカウントしません。
- 再送時間を3回目の重複ACKまたは予想されるRTOと比較し、輻輳状態、RTT、およびスタックの回復アルゴリズムを確認します。
- 1つのWiresharkラベルから根本原因を特定するのではなく、アプリケーションの遅延をインターフェースの損失、並べ替え、キューシグナルと関連付けます。
Wiresharkの tcp.analysis.fast_retransmission、tcp.analysis.retransmission、tcp.analysis.duplicate_ack の値は、利用可能なキャプチャからのアナライザの推論であり、TCPヘッダーに含まれるフラグではありません。TSOとGROにより、ホストキャプチャのセグメント境界がネットワーク上のパケットと異なる場合もあります。重要な結論には、両方のエンドポイントのシーケンス空間とタイミングのクロスチェックが必要です。
質の高い模範解答
「バイトシーケンス空間で導出します。Aは SEQ=1000 で長さが1,000であるため、Aを受信した後、受信側は次にバイト2,000を期待し、新しいACK 2,000を送信します。Bはバイト2,000から2,999をカバーし、失われます。C、D、Eはバイト3,000から5,999をカバーして到着しますが、いずれも2,000のギャップを埋めません。したがって、累積ACKは2,000のままです。順不同の各セグメントは1つの重複ACKを生成します。SACKがネゴシエートされていた場合、受信側はバイト3,000から5,999がバッファリングされていることも段階的に報告します。
古典的なRFC 5681の動作では、C、D、Eによって2,000に対する3つの重複ACKが生成されます。3回目の重複ACKにより、送信側はRTOを待たずにBを高速再送します。Bが到着すると、受信バッファは連続したものになり、累積ACKは直接6,000にジャンプします。よくある1つズレるエラーは、Aの後の最初のACK 2,000を重複としてカウントすることです。これは境界を進める新しいACKです。
損失が末尾にある場合やflightが小さすぎる場合、後続の3つのセグメントが存在しないため、RTOが最後のセーフティネットになります。RTOは平滑化されたRTTプラスRTT変動の4倍から推定され、RFC 6298はタイムアウト後に指数バックオフを適用します。ACKクロックが停止している可能性があるため、RTOは通常、高速回復よりも強い輻輳ウィンドウ削減を引き起こします。SACKは、どの不連続な上位バイトが到着したかを送信側に伝えます。これは特に複数のギャップがある場合に役立ちます。それ自体で累積ACKを進めることはなく、輻輳制御アルゴリズムでもありません。
実際のスタックでは、RACK-TLPを使用して送信時刻とSACKフィードバックから損失を推論し、末尾の損失をプローブする場合もあります。3つの重複ACKは必須の古典的導出ですが、すべてのトレースで唯一のトリガーになるわけではありません。診断では、両方のエンドポイントでシーケンス番号、長さ、累積ACK、SACKブロック、および再送時間を揃え、スタックとオフロードの設定を確認した上で、実際の損失を並べ替え、ACKパスの損失、またはアナライザの推論と区別します。」
よくある間違い
- ACKをパケット番号として扱う → TCPは連続したバイト空間を確認応答する →
SEQ + LENで次に期待されるバイトを導出する。 - Aの後のACK 2,000を重複1としてカウントする → 累積境界を初めて進めるものである → 境界を進めない同じ番号の後続ACKのカウントを開始する。
- CがACK 4,000を生成すると主張する → Bのバイトギャップが残っている → 累積ACKを2,000に維持し、SACKでCを報告する。
- SACKが累積ACKを置き換えると主張する → TCPは依然として連続した境界を累積的に進める → SACKを追加の不連続ブロック情報として扱う。
- 3つの重複が損失を証明すると主張する → 並べ替えや複製によっても同じシグナルが作成される可能性がある → これを古典的な損失ヒューリスティックと呼び、タイムラインを検証する。
- すべての損失が高速再送されると主張する → 末尾の損失や小さなflightでは十分な重複ACKが作成されない場合がある → RTOを保持し、RACK-TLPの改善点を説明する。
- 再送と輻輳制御を混同する → データの修復と送信レートの制限は異なる問題を解決する → 回復アクション、
cwnd、およびrwndを個別に述べる。 - Wiresharkラベルをネットワーク上のプロトコルビットとして扱う → ラベルはキャプチャコンテキストから推論されたものであり、オフロードによって歪められている可能性がある → 両方のエンドポイントのシーケンス空間とタイミングをクロスチェックする。
フォローアップの質問
フォローアップ1:2つのセグメントのみが送信され、2番目が失われた場合、高速再送は発生しますか?
古典的なパスでは発生しません。シーケンス番号の大きいデータが受信側に到達しないため、3つの重複ACKを生成できません。送信側は通常、RTOを待ちます。RACK-TLPスタックはフィードバックを求めるために末尾損失プローブ(tail-loss probe)を送信する場合がありますが、プローブが失敗した場合でもRTOがフォールバックを提供します。
フォローアップ2:Cが単に並べ替えられただけで、Bが元の送信で後から到着した場合はどうなりますか?
Cを受信すると、重複ACK 2,000と対応するSACKブロックが生成されます。損失閾値に達する前にBが到着した場合、累積ACKが進み、再送は回避されます。並べ替えが十分に大きく、最初に3つの重複がトリガーされた場合、古典的な高速再送はスプリアス(不要な再送)になる可能性があります。RACKは時間ベースの並べ替えウィンドウを使用して、この固定パケット閾値の失敗モードを減らします。
フォローアップ3:BとDの両方が失われた場合、なぜSACKの価値が高まるのですか?
受信側は配信されたCブロックとEブロックを報告できます。送信側はその情報をギャップスコアボードで使用し、SACKされた範囲をスキップして、BとDを修復します。SACKがない場合、累積ACKは最も左側のギャップのみを公開します。古典的なRenoは1つのウィンドウ内の複数の損失に関する情報が少なく、部分的なACKや最終的にはRTOが必要になる場合があります。
フォローアップ4:最初のRTTサンプルが120ミリ秒の場合、RTOを120ミリ秒に設定しないのはなぜですか?
RTTはキューやパスの変更によって変動します。RFC 6298は RTTVAR を R/2 に初期化します。G が 4 × RTTVAR 以下の場合、生のRTOは R + 4 × R/2 = 3R になります。ここでは360ミリ秒であり、RFCでは少なくとも1秒に切り上げることを推奨しています。1つのRTTを直接使用すると、小さな変動によってスプリアスなタイムアウトと不要な再送が発生します。
フォローアップ5:キャプチャで再送が確認されますが、3つの重複ACKはありません。これはRTOを証明しますか?
いいえ。キャプチャで逆方向パスのACKが欠落している可能性があり、観測ポイントがオフロードの前後である可能性があり、スタックがRACK-TLPまたは別の回復アルゴリズムを使用している可能性があります。再送を前回のACKタイミングと比較し、SACKと末尾プローブを検査し、送信側のカーネル状態を確認し、受信側のキャプチャで証拠を補完してください。
フォローアップ6:タイムアウト後の輻輳応答が通常、高速回復後よりも強いのはなぜですか?
3つの重複ACKは、後続のセグメントがネットワークを離れて受信側に到達していることを示すため、ACKクロックはまだ動作しています。RTOは、flight全体またはフィードバックパスが進捗しなかったことを意味する可能性があります。したがって、古典的なRFC 5681では、タイムアウト後に cwnd をフルサイズセグメント1つ分以下に減らしてスロースタートを再開しますが、高速回復では削減された送信ウィンドウを維持します。