RFC 9002 QUIC 損失検出 2021年5月
Iyengar & Swett 標準化過程 [ページ]
ストリーム:
Internet Engineering Task Force (IETF)
RFC:
9002
カテゴリ:
標準化過程
公開:
ISSN:
2070-1721
著者:
J. Iyengar,
Fastly
I. Swett,
Google

RFC 9002

QUIC の損失検出と輻輳制御

概要

この文書は、QUIC の損失検出および輻輳制御メカニズムについて 説明します。

このメモの位置付け

これはインターネット標準化過程の文書です。

この文書は Internet Engineering Task Force (IETF) の成果物です。これは IETF コミュニティの合意を 表しています。公開レビューを受けており、 Internet Engineering Steering Group (IESG) によって公開が承認されています。 インターネット標準に関する詳細情報は RFC 7841 の第2節で入手できます。

この文書の現在の状態、正誤表、および フィードバックを提供する方法に関する情報は、 https://www.rfc-editor.org/info/rfc9002 で入手できます。

目次

1. はじめに

QUIC は、安全な汎用トランスポートプロトコルであり、 [QUIC-TRANSPORT] で説明されています。この文書は、QUIC の損失 検出および輻輳 制御メカニズムについて説明します。

2. 表記規則と定義

この文書におけるキーワード「MUST」、「MUST NOT」、 「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、 「SHOULD NOT」、「RECOMMENDED」、「NOT RECOMMENDED」、「MAY」、および「OPTIONAL」は、 ここに示すようにすべて大文字で現れる場合に限り、BCP 14 [RFC2119] [RFC8174] で説明されているとおりに解釈されます。

この文書で使用される用語の定義:

ACK 誘発フレーム:

ACK、PADDING、および CONNECTION_CLOSE 以外のすべてのフレームは ACK 誘発とみなされます。

ACK 誘発パケット:

ACK 誘発フレームを含むパケットは、最大確認応答遅延内に受信者から ACK を 誘発し、ACK 誘発パケットと呼ばれます。

転送中パケット:

パケットは、ACK 誘発であるか PADDING フレームを含み、 送信済みであるが、確認応答されておらず、損失と宣言されておらず、 古い鍵とともに破棄されていない場合、転送中とみなされます。

3. QUIC 送信機構の設計

QUIC のすべての送信はパケットレベルのヘッダー付きで送られます。このヘッダーは 暗号化レベルを示し、パケットシーケンス番号(以下では パケット番号と呼ぶ)を含みます。暗号化レベルは、 Section 12.3 of [QUIC-TRANSPORT] で説明されているように、パケット番号空間を示します。パケット番号は コネクションの存続期間中、1 つのパケット番号空間内では決して 繰り返されません。パケット番号は空間内で単調増加順に送信され、 曖昧さを防ぎます。一部のパケット番号が使用されず、意図的な ギャップを残すことは許可されています。

この設計により、送信と再送信を区別する必要がなくなります。 これにより、TCP の損失検出メカニズムを QUIC が解釈する際の 大きな複雑さが取り除かれます。

QUIC パケットは、異なる型の複数のフレームを含むことができます。回復 メカニズムは、信頼性のある配送が必要なデータおよびフレームが 確認応答されるか、損失と宣言され、必要に応じて新しいパケットで 送信されることを保証します。パケットに含まれるフレームの型は、 回復および輻輳制御ロジックに影響します:

4. QUIC と TCP の関連する相違点

TCP の損失検出および輻輳制御に詳しい読者は、 ここに、よく知られた TCP のものと並行するアルゴリズムを見つけるでしょう。 しかし、QUIC と TCP のプロトコル上の違いは、アルゴリズム上の違いに つながります。これらのプロトコル上の違いを以下に簡潔に説明します。

4.1. 分離されたパケット番号 空間

QUIC は各暗号化レベルに対して個別のパケット番号空間を使用します。 ただし、0-RTT とすべての世代の 1-RTT 鍵は同じパケット 番号空間を使用します。個別のパケット番号空間により、 ある暗号化レベルで送信されたパケットの確認応答が、 別の暗号化レベルで送信されたパケットの 偽の再送信を引き起こさないことが保証されます。輻輳制御と ラウンドトリップ時間(RTT) 測定は、パケット番号空間をまたいで統合されます。

4.2. 単調増加する パケット番号

TCP は送信者での送信順序と受信者での配送順序を混同しており、 その結果、再送信の曖昧性問題 [RETRANSMISSION] が生じます。QUIC は 送信順序を配送順序から分離します。 パケット番号は送信順序を示し、配送順序は STREAM フレーム内の ストリームオフセットによって決定されます。

QUIC のパケット番号は、パケット番号空間内で厳密に増加し、 送信順序を直接符号化します。より大きなパケット番号は、 そのパケットが後で送信されたことを意味し、より小さなパケット番号は、 そのパケットが先に送信されたことを意味します。ACK 誘発 フレームを含むパケットが損失と検出された場合、QUIC は必要なフレームを 新しいパケット番号を持つ新しいパケットに含めます。これにより、ACK を受信したときに どのパケットが確認応答されたのかに関する曖昧さが取り除かれます。その結果、 より正確な RTT 測定が可能となり、偽の再送信は容易に検出され、Fast Retransmit などの メカニズムを、パケット番号のみに基づいて普遍的に適用できます。

この設計上の要点は、QUIC の損失検出メカニズムを大幅に簡素化します。 ほとんどの TCP メカニズムは、TCP シーケンス番号に基づいて 送信順序を暗黙に推定しようとします。これは、特に TCP タイムスタンプが 利用できない場合には、容易ではない作業です。

4.3. より明確な損失エポック

QUIC は、パケットが失われると損失エポックを開始します。損失エポックは、 そのエポックの開始後に送信されたいずれかのパケットが確認応答されると終了します。 TCP はシーケンス番号空間のギャップが埋まるのを待つため、セグメントが 連続して複数回失われると、損失エポックが複数のラウンドトリップにわたって 終了しないことがあります。どちらもエポックごとに輻輳ウィンドウを 1 回だけ 減らすべきであるため、QUIC は損失を経験した各ラウンドトリップごとに 1 回 それを行う一方、TCP は複数のラウンドトリップ全体で 1 回しか行わない場合があります。

4.4. Reneging なし

QUIC ACK フレームには、TCP の Selective Acknowledgments(SACKs)[RFC2018] と同様の情報が含まれます。しかし、 QUIC はパケット 確認応答の reneging を許可しないため、両側の実装が大幅に簡素化され、 送信者のメモリ負荷が軽減されます。

4.5. より多くの ACK 範囲

QUIC は、TCP の 3 つの SACK 範囲とは対照的に、多数の ACK 範囲をサポートします。 損失の多い環境では、これにより回復が速くなり、偽の再送信が減少し、 タイムアウトに依存することなく前進が保証されます。

4.6. 遅延確認応答に対する明示的な補正

QUIC エンドポイントは、パケットが受信された時点から 対応する確認応答が送信される時点までに発生した遅延を測定します。 これにより、ピアはより正確な RTT 推定値を維持できます。Section 13.2 of [QUIC-TRANSPORT] を参照してください。

4.7. プローブタイムアウトが RTO と TLP を置き換える

QUIC は、TCP の再送信タイムアウト(RTO)計算に基づくタイマーを持つ プローブタイムアウト(PTO。Section 6.2 を参照)を使用します。 [RFC6298] を参照してください。QUIC の PTO は、 固定の最小タイムアウトを使用する代わりに、ピアが期待する最大確認応答遅延を 含みます。

TCP の RACK-TLP 損失検出アルゴリズム [RFC8985] と同様に、QUIC は PTO が満了しても輻輳ウィンドウを縮小しません。これは、末尾の単一パケットの 損失が持続的輻輳を示すものではないためです。代わりに、QUIC は 持続的輻輳が宣言されたときに輻輳ウィンドウを縮小します。 Section 7.6 を参照してください。これにより、QUIC は不要な輻輳 ウィンドウ削減を回避し、Forward RTO-Recovery(F-RTO)[RFC5682] のような補正メカニズムの必要性をなくします。QUIC は PTO 満了時に輻輳 ウィンドウを縮小しないため、QUIC 送信者は、利用可能な輻輳 ウィンドウがまだある場合、PTO 満了後にさらに 転送中パケットを送信することを制限されません。これは、送信者がアプリケーション制限状態で PTO タイマーが満了した場合に発生します。これは、アプリケーション制限状態では TCP の RTO メカニズムより 積極的ですが、アプリケーション制限状態でない場合は同一です。

QUIC は、タイマーが満了したときに、プローブパケットが一時的に 輻輳ウィンドウを超えることを許可します。

4.8. 最小輻輳 ウィンドウは 2 パケット

TCP は 1 パケットの最小輻輳ウィンドウを使用します。しかし、その単一 パケットが失われると、送信者は回復のために PTO を待つ必要があります(Section 6.2)。 これは RTT よりはるかに長くなる可能性があります。単一の ACK 誘発パケットを送信することは、 受信者が確認応答を遅延させる場合に追加の遅延が発生する可能性も 高めます。

そのため QUIC は、最小輻輳ウィンドウを 2 パケットにすることを推奨します。これはネットワーク負荷を増加させますが、 持続的輻輳の下では送信者が送信レートを指数的に低下させ続けるため 安全とみなされます(Section 6.2)。

4.9. ハンドシェイクパケットは 特別ではない

TCP は SYN または SYN-ACK パケットの損失を持続的輻輳として扱い、 輻輳ウィンドウを 1 パケットに減らします。[RFC5681] を参照してください。QUIC は ハンドシェイクデータを含むパケットの損失を、他の損失と同じように扱います。

5. ラウンドトリップ時間の推定

高いレベルでは、エンドポイントは、パケットが送信されてから 確認応答されるまでの時間を RTT サンプルとして測定します。エンドポイントは RTT サンプルと ピアが報告したホスト遅延(Section 13.2 of [QUIC-TRANSPORT] を参照)を使用して、 ネットワークパスの RTT の統計的記述を生成します。エンドポイントは各パスについて 次の 3 つの値を計算します。一定期間にわたる最小値 (min_rtt)、指数加重移動平均(smoothed_rtt)、および 観測された RTT サンプルの平均偏差(この文書の残りでは「variation」と呼ぶ) (rttvar)です。

5.1. RTT サンプルの生成

エンドポイントは、次の 2 つの条件を満たす ACK フレームを受信したときに RTT サンプルを生成します:

  • 最大の確認応答済みパケット番号が新たに 確認応答されていること、および
  • 新たに確認応答されたパケットの少なくとも 1 つが ACK 誘発であること。

RTT サンプル latest_rtt は、最大の 確認応答済みパケットが送信されてから経過した時間として生成されます:

latest_rtt = ack_time - send_time_of_largest_acked

RTT サンプルは、受信した ACK フレーム内の最大の確認応答済みパケットのみを 使用して生成されます。これは、ピアが ACK フレーム内で最大の 確認応答済みパケットについてのみ確認応答遅延を報告するためです。報告された 確認応答遅延は RTT サンプル測定には使用されませんが、 以後の smoothed_rtt および rttvar の計算で RTT サンプルを調整するために 使用されます(Section 5.3)。

単一のパケットに対して複数の RTT サンプルが生成されることを避けるため、 ACK フレームは、最大の確認応答済みパケットを新たに確認応答していない場合、 RTT 推定値の更新に使用SHOULD NOT

少なくとも 1 つの ACK 誘発パケットを新たに確認応答していない ACK フレームを受信した場合、RTT サンプルを生成しては MUST NOT。 ピアは通常、非 ACK 誘発パケットのみを受信した場合には ACK フレームを 送信しません。したがって、非 ACK 誘発パケットのみの確認応答を含む ACK フレームは、任意に大きな ACK Delay 値を含む可能性があります。 そのような ACK フレームを無視することで、以後の smoothed_rtt および rttvar の計算における複雑さを避けられます。

送信者は、1 RTT 内に複数の ACK フレームを受信した場合、RTT ごとに 複数の RTT サンプルを生成することがあります。[RFC6298] で示唆されているように、そうすると smoothed_rtt および rttvar に十分な履歴が残らない可能性があります。 RTT 推定値が十分な履歴を保持することを保証する方法は、未解決の研究課題です。

5.2. min_rtt の推定

min_rtt は、あるネットワーク パスについて一定期間に観測された最小 RTT に対する送信者の推定値です。 この文書では、min_rtt は損失検出において、あり得ないほど小さい RTT サンプルを 拒否するために使用されます。

min_rtt は、最初の RTT サンプルでは latest_rtt に設定されMUST。 min_rtt は、他のすべての サンプルで min_rtt と latest_rtt(Section 5.1)の小さい方に 設定されMUST

エンドポイントは、min_rtt の計算においてローカルで観測された時間のみを使用し、 ピアが報告した確認応答遅延では調整しません。そうすることで、 エンドポイントは自らの観測だけに基づいて smoothed_rtt の下限を設定でき (Section 5.3 を参照)、 ピアによって誤って報告された遅延による 過小評価の可能性を制限します。

ネットワークパスの RTT は時間とともに変化することがあります。パスの実際の RTT が 減少した場合、min_rtt は最初の低いサンプルで直ちに適応します。しかし、 パスの実際の RTT が増加した場合、min_rtt はそれに適応しません。そのため、 新しい RTT よりも小さい将来の RTT サンプルが smoothed_rtt に含まれることが許されます。

エンドポイントは、持続的 輻輳が確立された後、min_rtt を最新の RTT サンプルに設定する SHOULD。これにより、RTT が増加したときに持続的 輻輳を繰り返し宣言することを避けられます。また、破壊的なネットワークイベント後に コネクションが min_rtt と smoothed_rtt の推定値をリセットできるようになります。 Section 5.3 を参照してください。

エンドポイントは、コネクション中の他の 時点、たとえばトラフィック量が少なく、低い 確認応答遅延を伴う確認応答を受信したときに、min_rtt を再確立しても MAY。 実装は、パスの実際の最小 RTT が 頻繁には観測できないため、min_rtt 値を頻繁に更新しすぎるべきではありません(SHOULD NOT)。

5.3. smoothed_rtt と rttvar の推定

smoothed_rtt はエンドポイントの RTT サンプルの指数加重移動平均であり、rttvar は平均 偏差を使用して RTT サンプルの変動を推定します。

smoothed_rtt の計算では、確認応答遅延で調整された後の RTT サンプルを使用します。これらの遅延は、 Section 19.3 of [QUIC-TRANSPORT] で説明されているように ACK フレームの ACK Delay フィールドからデコードされます。

ピアは、ハンドシェイク中に、ピアの max_ack_delay より大きい確認応答遅延を報告することがあります(Section 13.2.1 of [QUIC-TRANSPORT])。これを 考慮するため、エンドポイントは、Section 4.1.2 of [QUIC-TLS] で定義されているように、ハンドシェイクが 確認されるまで max_ack_delay を無視する SHOULD。そのような大きな確認応答遅延が発生する場合、 それらは繰り返し発生せず、ハンドシェイクに限定される可能性が高いです。 したがって、エンドポイントはそれらを max_ack_delay で制限せずに使用でき、 RTT 推定値の不要な膨張を避けられます。

確認応答遅延の報告におけるピアの誤り、またはエンドポイントの min_rtt 推定の誤りがある場合、大きな確認応答遅延は smoothed_rtt を大幅に膨張させる可能性があることに注意してください。したがって、 ハンドシェイク確認前、エンドポイントは、確認応答遅延で RTT サンプルを調整した結果、そのサンプルが min_rtt より小さくなる場合、RTT サンプルを無視しても MAY

ハンドシェイクが確認された後、ピアが報告した確認応答遅延のうち ピアの max_ack_delay より大きいものは、 ピアでのスケジューラー遅延や以前の確認応答の損失など、意図しないが 繰り返し発生する可能性のある遅延に起因するとみなされます。過剰な遅延は、 準拠していない受信者によるものである可能性もあります。したがって、これらの追加遅延は 実質的にパス遅延の一部とみなされ、RTT 推定値に組み込まれます。

したがって、ピアが報告した確認応答 遅延を使用して RTT サンプルを調整する場合、エンドポイントは:

  • Initial パケットについては、これらの 確認応答はピアによって遅延されないため (Section 13.2.1 of [QUIC-TRANSPORT])、 確認応答遅延を無視しても MAY
  • ハンドシェイクが確認されるまで、ピアの max_ack_delay を 無視する SHOULD
  • ハンドシェイクが確認された後は、確認応答遅延とピアの max_ack_delay の小さい方を使用し MUST。そして
  • その結果の値が min_rtt より小さくなる場合、 RTT サンプルから確認応答遅延を差し引いては MUST NOT。これは、誤報告するピアによる smoothed_rtt の 過小評価を制限します。

さらに、対応する復号鍵がすぐに利用できない場合、エンドポイントは 確認応答の処理を延期することがあります。たとえば、クライアントは、 1-RTT パケット保護鍵がまだ利用できないために復号できない 0-RTT パケットに対する確認応答を受信することがあります。 そのような場合、エンドポイントは、ハンドシェイクが確認されるまで、 そのようなローカル遅延を RTT サンプルから差し引く SHOULD

[RFC6298] と同様に、 smoothed_rtt と rttvar は次のように計算されます。

エンドポイントは、コネクション確立時、およびコネクション移行中に推定器がリセットされるときに RTT 推定器を初期化します。Section 9.4 of [QUIC-TRANSPORT] を参照してください。新しいパスについて RTT サンプルが利用可能になる前、または推定器がリセットされた場合、 推定器は初期 RTT を使用して初期化されます。 Section 6.2.2 を参照してください。

smoothed_rtt と rttvar は、kInitialRtt が 初期 RTT 値を含むものとして、次のように初期化されます:

smoothed_rtt = kInitialRtt
rttvar = kInitialRtt / 2

ネットワークパスの RTT サンプルは latest_rtt に記録されます。 Section 5.1 を参照してください。初期化後の最初の RTT サンプルで、 推定器はそのサンプルを使用して リセットされます。これにより、推定器が過去のサンプルの履歴を保持しないことが保証されます。 他のパスで送信されたパケットは、現在のパスの RTT サンプルには寄与しません。 Section 9.4 of [QUIC-TRANSPORT] で説明されているとおりです。

初期化後の最初の RTT サンプルで、smoothed_rtt と rttvar は 次のように設定されます:

smoothed_rtt = latest_rtt
rttvar = latest_rtt / 2

以後の RTT サンプルで、smoothed_rtt と rttvar は次のように変化します:

ack_delay = decoded acknowledgment delay from ACK frame
if (handshake confirmed):
  ack_delay = min(ack_delay, max_ack_delay)
adjusted_rtt = latest_rtt
if (latest_rtt >= min_rtt + ack_delay):
  adjusted_rtt = latest_rtt - ack_delay
smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt
rttvar_sample = abs(smoothed_rtt - adjusted_rtt)
rttvar = 3/4 * rttvar + 1/4 * rttvar_sample

6. 損失検出

QUIC 送信者は、確認応答を使用して失われたパケットを検出し、PTO を使用して 確認応答が受信されることを保証します。Section 6.2 を参照してください。この節では、 これらのアルゴリズムについて説明します。

パケットが失われた場合、QUIC トランスポートはその損失から回復する必要があります。 たとえば、データを再送信する、更新されたフレームを送信する、または フレームを破棄します。詳細については、Section 13.3 of [QUIC-TRANSPORT] を参照してください。

損失検出は、RTT 測定および 輻輳制御とは異なり、パケット番号空間ごとに分離されています。これは、RTT と輻輳制御が パスの特性である一方、損失検出は鍵の可用性にも依存するためです。

6.1. 確認応答に基づく 検出

確認応答に基づく損失検出は、TCP の Fast Retransmit [RFC5681]、Early Retransmit [RFC5827]、Forward Acknowledgment [FACK]、SACK loss recovery [RFC6675]、および RACK-TLP [RFC8985] の精神を実装します。この 節では、これらのアルゴリズムが QUIC でどのように実装されるかの概要を示します。

パケットは、次のすべての条件を満たす場合に損失と宣言されます:

  • そのパケットが未確認応答であり、転送中であり、かつ 確認応答されたパケットより前に送信されていること。
  • そのパケットが確認応答されたパケットより kPacketThreshold パケット前に送信されている (Section 6.1.1)、または十分に過去に 送信されていること (Section 6.1.2)。

確認応答は、後で送信されたパケットが配送されたことを示し、パケットしきい値と 時間しきい値は、パケットの並べ替えに対するある程度の許容を提供します。

パケットを誤って損失と宣言すると、不要な再送信につながり、 損失検出時の輻輳制御器の動作によって性能が低下する可能性があります。 実装は、偽の再送信を検出し、将来の偽の再送信と損失イベントを 減らすために、パケットまたは時間の並べ替えしきい値を増やすことができます。 適応的な時間しきい値を持つ実装は、回復遅延を最小化するために、 より小さい初期並べ替え しきい値から開始することを選択しても MAY

6.1.1. パケットしきい値

パケット並べ替えしきい値 (kPacketThreshold)の初期値として RECOMMENDED される値は 3 であり、これは TCP 損失検出のベストプラクティス [RFC5681] [RFC6675] に基づきます。TCP と同様であり続けるため、 実装は 3 未満のパケットしきい値を使用するべきではありません (SHOULD NOT)。[RFC5681] を参照してください。

一部のネットワークではより高い程度のパケット並べ替えが発生し、 送信者が偽の損失を検出することがあります。さらに、QUIC では TCP よりも パケット並べ替えが一般的である可能性があります。これは、TCP パケットを観測し並べ替えることができるネットワーク要素が QUIC ではそれを行えず、 また QUIC パケット番号が暗号化されているためです。RACK [RFC8985] のように、損失を誤検出した後に並べ替えしきい値を増やす アルゴリズムは TCP で有用であることが証明されており、 QUIC でも少なくとも同じ程度に有用であると期待されます。

6.1.2. 時間しきい値

同じパケット番号空間内の後続パケットが確認応答された後、 エンドポイントは、以前のパケットがしきい値 量の時間だけ過去に送信されていた場合、そのパケットを損失と宣言する SHOULD。パケットを早すぎる時点で損失と宣言することを避けるため、 この時間しきい値は、kGranularity 定数で示されるローカルタイマー 粒度以上に設定され MUST。時間しきい値は次のとおりです:

max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)

最大の確認応答済みパケットより前に送信されたパケットをまだ 損失と宣言できない場合、残り時間に対してタイマーを設定する SHOULD

max(smoothed_rtt, latest_rtt) を使用することで、次の 2 つの 場合から保護されます:

  • 最新の RTT サンプルが smoothed RTT より低い場合。これは、確認応答がより短いパスを通った 並べ替えによる可能性があります。
  • 最新の RTT サンプルが smoothed RTT より高い場合。これは、実際の RTT が持続的に増加しているが、 smoothed RTT がまだ追いついていないためである可能性があります。

RTT 乗数として表される 時間しきい値(kTimeThreshold)の RECOMMENDED 値は 9/8 です。タイマー粒度 (kGranularity)の RECOMMENDED 値は 1 ミリ秒です。

実装は、絶対しきい値、以前のコネクションからのしきい値、 適応的しきい値、または RTT 変動を含めることを試しても MAY。 小さいしきい値は並べ替えに対する耐性を低下させ、偽の 再送信を増やします。一方、大きいしきい値は損失検出遅延を増加させます。

6.2. プローブタイムアウト

Probe Timeout(PTO)は、ACK 誘発パケットが期待される期間内に 確認応答されない場合、またはサーバーがクライアントのアドレスを検証していない可能性がある場合に、 1 つまたは 2 つのプローブデータグラムの送信を引き起こします。PTO により、 コネクションは末尾パケットまたは確認応答の損失から回復できます。

損失検出と同様に、PTO はパケット番号空間ごとです。つまり、 PTO 値はパケット番号空間ごとに計算されます。

PTO タイマー満了イベントはパケット損失を示すものではなく、 以前の未確認応答パケットを損失としてマークする原因となっては MUST NOT。 パケットを新たに確認応答する確認応答を受信した場合、 損失検出はパケットしきい値および時間しきい値メカニズムによって指示されるとおりに進みます。 Section 6.1 を参照してください。

QUIC で使用される PTO アルゴリズムは、TCP の Tail Loss Probe [RFC8985]、RTO [RFC5681]、および F-RTO アルゴリズム [RFC5682] の信頼性機能を実装します。タイムアウト計算は TCP の RTO 期間 [RFC6298] に基づきます。

6.2.1. PTO の計算

ACK 誘発パケットが送信されると、送信者は 次のように PTO 期間のタイマーをスケジュールします:

PTO = smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay

PTO 期間は、送信者が送信済みパケットの 確認応答を待つべき時間量です。この期間には、推定された ネットワーク RTT(smoothed_rtt)、その推定値の変動(4*rttvar)、 および受信者が確認応答の送信を遅延させ得る最大時間を考慮するための max_ack_delay が含まれます。

PTO が Initial または Handshake パケット番号空間に対してセットされる場合、 PTO 期間計算における max_ack_delay は 0 に設定されます。これは、ピアがこれらのパケットを 意図的に遅延させないことが期待されるためです。Section 13.2.1 of [QUIC-TRANSPORT] を参照してください。

PTO 期間は、タイマーが 即座に満了することを避けるため、少なくとも kGranularity でなければなりません(MUST)。

複数のパケット番号空間で ACK 誘発パケットが転送中の場合、 タイマーは Initial および Handshake パケット 番号空間のより早い値に設定され MUST

エンドポイントは、ハンドシェイクが確認されるまで、 Application Data パケット番号 空間に対して PTO タイマーを設定しては MUST NOT。そうすることで、ピアがまだそれらを処理する鍵を持っていない、 またはエンドポイントがそれらの確認応答を処理する鍵をまだ持っていない場合に、 エンドポイントがパケット内の情報を再送信することを防ぎます。 たとえば、クライアントがサーバーに 0-RTT パケットを送信するとき、 サーバーがそれらを復号できるかどうかを知らずに送信します。 同様に、サーバーが、クライアントがサーバー証明書を検証して これらの 1-RTT パケットを読めることを確認する前に、1-RTT パケットを送信する場合にも これが起こる可能性があります。

送信者は、ACK 誘発パケットが 送信または確認応答されるたび、または Initial もしくは Handshake 鍵が破棄されるとき (Section 4.9 of [QUIC-TLS])、PTO タイマーを再起動する SHOULD。これにより、PTO は常に 最新の RTT 推定値に基づき、パケット 番号空間をまたいで正しいパケットに対して設定されます。

PTO タイマーが満了すると、PTO バックオフは増加され MUST、その結果、 PTO 期間は現在の値の 2 倍に設定されます。PTO バックオフ係数は、 次の場合を除き、確認応答を受信したときにリセットされます。サーバーは ハンドシェイク中に、通常よりもパケットへの応答に時間がかかることがあります。 そのようなサーバーを繰り返されるクライアントプローブから保護するため、 クライアントがサーバーによるクライアントアドレスの検証完了をまだ確信していない場合、 PTO バックオフはリセットされません。つまり、クライアントは Initial パケットで確認応答を受信しても PTO バックオフ係数をリセットしません。

送信者レートのこの指数的な低下は重要です。 連続した PTO は、深刻な 輻輳によるパケットまたは確認応答の損失によって引き起こされる可能性があるためです。 複数のパケット番号空間で ACK 誘発パケットが転送中であっても、 PTO の指数的増加はすべての空間にまたがって発生し、 ネットワークへの過剰な負荷を防ぎます。たとえば、Initial パケット番号空間でのタイムアウトは、Handshake パケット 番号空間のタイムアウトの長さを 2 倍にします。

連続した PTO が満了する総時間の長さは、 アイドルタイムアウトによって制限されます。

時間しきい値 損失検出のためのタイマーが設定されている場合、PTO タイマーは設定されては MUST NOTSection 6.1.2 を参照してください。時間 しきい値損失検出のために設定されたタイマーは、ほとんどの場合 PTO タイマーより早く満了し、データを偽って再送信する可能性が低くなります。

6.2.2. ハンドシェイクと 新しいパス

同じネットワーク上の再開されたコネクションは、以前のコネクションの 最終的な smoothed RTT 値を再開されたコネクションの初期 RTT として使用しても MAY。以前の RTT が利用できない場合、初期 RTT は 333 ミリ秒に設定される SHOULD。 これにより、TCP の初期 RTO として推奨されるように、 ハンドシェイクは 1 秒の PTO から開始されます。Section 2 of [RFC6298] を参照してください。

コネクションは、PATH_CHALLENGE の送信から PATH_RESPONSE の受信までの遅延を使用して、新しいパスの初期 RTT (Appendix A.2 の kInitialRtt を参照)を設定しても MAY ですが、その 遅延は RTT サンプルとみなすべきではありません(SHOULD NOT)。

Initial 鍵と Handshake 鍵が破棄されると (Section 6.4 を参照)、Initial パケットと Handshake パケットはもはや確認応答され得ないため、 bytes in flight から削除されます。Initial または Handshake 鍵が破棄された場合、 PTO および損失 検出タイマーはリセットされ MUST。これは、鍵の破棄が 前進を示し、損失検出タイマーが すでに破棄されたパケット番号空間に対して設定されていた可能性があるためです。

6.2.2.1. アドレス 検証前

サーバーがそのパス上でクライアントのアドレスを検証するまで、 送信できるデータ量は、受信したデータ量の 3 倍に制限されます。 Section 8.1 of [QUIC-TRANSPORT] で規定されています。追加のデータを送信できない場合、 PTO 上で送信されるパケットは 増幅防止制限に算入されるため、クライアントからデータグラムを 受信するまで、サーバーの PTO タイマーはセットされては MUST NOT

サーバーがクライアントからデータグラムを受信すると、 増幅制限が増加し、サーバーは PTO タイマーをリセットします。その後 PTO タイマーが 過去の時刻に設定された場合、それは直ちに実行されます。そうすることで、 ハンドシェイク完了に不可欠なパケットより前に新しい 1-RTT パケットを送信することを避けられます。 特に、0-RTT が受け入れられたが、サーバーがクライアントのアドレスを 検証できない場合に、これが起こる可能性があります。

サーバーは、クライアントからさらにデータグラムを受信するまで ブロックされる可能性があるため、サーバーのアドレス検証が完了したことが 確実になるまで、サーバーをブロック解除するためのパケットを送信するのは クライアントの責任です(Section 8 of [QUIC-TRANSPORT] を参照)。つまり、クライアントは、 Handshake パケットのいずれについても確認応答を受信しておらず、 ハンドシェイクが確認されていない場合(Section 4.1.2 of [QUIC-TLS] を参照)、 転送中のパケットがない場合でも PTO タイマーを設定し MUST。 PTO が発火した場合、クライアントは Handshake 鍵を持っていれば Handshake パケットを送信し MUST、そうでなければ 少なくとも 1200 バイトのペイロードを持つ UDP データグラムで Initial パケットを送信し MUST

6.2.3. ハンドシェイク完了の 高速化

サーバーが重複した CRYPTO データを含む Initial パケットを受信した場合、 クライアントがサーバーの Initial パケットで送信された CRYPTO データをすべて 受信していない、またはクライアントの推定 RTT が小さすぎると 推定できます。クライアントが Handshake 鍵を取得する前に Handshake または 1-RTT パケットを受信した場合、 サーバーの Initial パケットの一部またはすべてが失われたと推定することがあります。

これらの条件下でハンドシェイク完了を高速化するため、エンドポイントは、 コネクションごとに限られた回数だけ、PTO 満了前に 未確認応答の CRYPTO データを含むパケットを送信しても MAY。ただし、Section 8.1 of [QUIC-TRANSPORT] のアドレス 検証制限に従います。コネクションごとに最大 1 回そうするだけで、 単一パケット損失から迅速に回復するには十分です。 処理できないパケットを受信したことに応答して常にパケットを再送信する エンドポイントは、無限のパケット交換を生み出す危険があります。

エンドポイントは、各データグラムが少なくとも 1 つの 確認応答を誘発することを保証するため、結合パケット(Section 12.2 of [QUIC-TRANSPORT] を参照)も使用できます。 たとえば、クライアントは PING および PADDING フレームを含む Initial パケットを 0-RTT データパケットと結合でき、サーバーは PING フレームを含む Initial パケットを最初のフライト内の 1 つ以上のパケットと結合できます。

6.2.4. プローブ パケットの送信

PTO タイマーが満了した場合、送信者はプローブとして そのパケット番号空間で少なくとも 1 つの ACK 誘発パケットを送信し MUST。 エンドポイントは、単一のデータグラム損失による高コストな 連続 PTO 満了を避けるため、または複数のパケット番号空間からデータを 送信するために、ACK 誘発パケットを含む最大 2 つの フルサイズデータグラムを送信しても MAY。 PTO で送信されるすべてのプローブパケットは ACK 誘発でなければなりません(MUST)。

タイマーが満了したパケット番号空間でデータを送信することに加えて、 送信者は、可能であればパケットを結合しながら、転送中データを持つ 他のパケット番号空間から ACK 誘発パケットを送信する SHOULD。これは、 サーバーが Initial と Handshake の両方のデータを転送中に持つ場合、または クライアントが Handshake と Application Data の両方を転送中に持つ場合に 特に有用です。ピアが 2 つのパケット番号 空間の一方についてのみ受信鍵を持っている可能性があるためです。

送信者が PTO 上でより速い確認応答を誘発したい場合、 確認応答遅延をなくすために パケット番号をスキップできます。

エンドポイントは、PTO 満了時に送信されるパケットに 新しいデータを含める SHOULD。 新しいデータを送信できない場合、以前に送信されたデータを送信しても MAY。 実装は、アプリケーションの 優先度に基づいて新しいデータまたは再送信データを送信することを含む、 プローブパケットの内容を決定するための代替戦略を使用しても MAY

送信者が送信する新しいデータも以前送信されたデータも持たないことがあります。 例として、次の一連のイベントを考えます。新しいアプリケーションデータが STREAM フレームで送信され、損失とみなされ、その後新しいパケットで再送信され、 さらに元の送信が確認応答されます。送信するデータがない場合、 送信者は単一の パケットで PING またはその他の ACK 誘発フレームを送信し、 PTO タイマーを再セットする SHOULD

あるいは、ACK 誘発パケットを送信する代わりに、 送信者は転送中のままの任意のパケットを損失としてマークしても MAY。 そうすることで追加パケットの送信を避けられますが、損失が過度に積極的に宣言され、 輻輳制御器による不要なレート低下を招くリスクが高まります。

連続する PTO 期間は指数的に増加し、その結果、 ネットワーク内でパケットが破棄され続けると、コネクション 回復遅延も指数的に増加します。PTO 満了時に 2 つのパケットを送信することで、 パケット破棄に対する耐性が高まり、連続 PTO イベントの確率が低下します。

PTO タイマーが複数回満了し、新しいデータを送信できない場合、 実装は毎回同じペイロードを送信するか、 異なるペイロードを送信するかを選択しなければなりません。 同じペイロードを送信する方が単純な場合があり、 最も優先度の高いフレームが先に到着することを保証します。毎回異なる ペイロードを送信すると、偽の再送信の可能性が減少します。

6.3. Retry パケットの処理

Retry パケットは、クライアントに別の Initial パケットを送信させ、 実質的にコネクション処理を再開します。Retry パケットは、Initial パケットが受信されたが処理されなかったことを示します。Retry パケットは、 パケットが処理されたことを示さず、パケット番号も 指定しないため、確認応答として扱うことはできません。

Retry パケットを受信したクライアントは、保留中のタイマーのリセットを含め、 輻輳制御および損失回復 状態をリセットします。その他のコネクション状態、特に 暗号ハンドシェイクメッセージは保持されます。 Section 17.2.5 of [QUIC-TRANSPORT] を参照してください。

クライアントは、最初の Initial パケットが送信された時点から Retry または Version Negotiation パケットが受信された時点までの期間として、サーバーへの RTT 推定値を計算しても MAY。 クライアントは、この値を初期 RTT 推定値のデフォルトの代わりに使用しても MAY

6.4. 鍵と パケット状態の破棄

Initial および Handshake パケット保護鍵が破棄されると (Section 4.9 of [QUIC-TLS] を参照)、それらの鍵で 送信されたすべてのパケットは、確認応答を処理できないため、 もはや確認応答され得ません。 送信者は、それらのパケットに関連付けられたすべての回復状態を破棄し MUST、 bytes in flight のカウントからそれらを削除し MUST

エンドポイントは、 Handshake パケットの交換を開始すると Initial パケットの送受信を停止します。 Section 17.2.2.1 of [QUIC-TRANSPORT] を参照してください。この時点で、 すべての転送中 Initial パケットの回復状態は破棄されます。

0-RTT が拒否された場合、すべての転送中 0-RTT パケットの回復状態は 破棄されます。

サーバーが 0-RTT を受け入れるが、Initial パケットより前に到着する 0-RTT パケットをバッファしない場合、早期の 0-RTT パケットは損失と宣言されますが、 これは頻繁ではないと期待されます。

鍵は、それらで暗号化されたパケットが確認応答されるか 損失と宣言された後のある時点で破棄されることが期待されます。しかし、 Initial および Handshake シークレットは、Handshake および 1-RTT 鍵がクライアントとサーバーの両方で利用可能であることが証明されるとすぐに 破棄されます。Section 4.9.1 of [QUIC-TLS] を参照してください。

7. 輻輳制御

この文書は、TCP NewReno [RFC6582] と同様の QUIC 用の送信者側輻輳制御器を規定します。

QUIC が輻輳制御のために提供するシグナルは汎用的であり、 異なる送信者側アルゴリズムをサポートするように設計されています。送信者は、 CUBIC [RFC8312] など、使用する 別のアルゴリズムを一方的に選択できます。

送信者がこの文書で規定されたものとは異なる制御器を使用する場合、 選択された制御器は、Section 3.1 of [RFC8085] で規定されている輻輳制御ガイドラインに 適合しなければなりません(MUST)。

TCP と同様に、ACK フレームのみを含むパケットは bytes in flight に算入されず、輻輳制御の対象にもなりません。TCP とは異なり、QUIC は これらのパケットの損失を検出でき、その情報を使用して輻輳 制御器または送信される ACK のみのパケットのレートを調整しても MAY ですが、この文書では そのためのメカニズムは説明しません。

輻輳制御器はパスごとであるため、他のパスで送信されたパケットは 現在のパスの輻輳制御器を変更しません。これは Section 9.4 of [QUIC-TRANSPORT] で説明されています。

この文書のアルゴリズムは、制御器の輻輳 ウィンドウをバイト単位で規定し、使用します。

エンドポイントは、そのパケットによって bytes_in_flight(Appendix B.2 を参照)が 輻輳ウィンドウより大きくなる場合、そのパケットを送信しては MUST NOT。ただし、そのパケットが PTO タイマー満了時(Section 6.2 を参照)または回復に入るとき (Section 7.3.2 を参照)に送信される場合を除きます。

7.1. 明示的輻輳 通知

パスが Explicit Congestion Notification(ECN) [RFC3168] [RFC8311] をサポートすることが検証されている場合、QUIC は IP ヘッダー内の Congestion Experienced(CE)コードポイントを輻輳のシグナルとして扱います。 この文書は、ピアが報告する ECN-CE カウントが増加したときの エンドポイントの応答を規定します。Section 13.4.2 of [QUIC-TRANSPORT] を参照してください。

7.2. 初期および最小 輻輳ウィンドウ

QUIC は、すべてのコネクションを、輻輳ウィンドウを 初期値に設定したスロースタートで開始します。エンドポイントは、 最大データグラムサイズ(max_datagram_size)の 10 倍の初期輻輳ウィンドウを 使用する SHOULD ですが、そのウィンドウを 14,720 バイトまたは最大データグラムサイズの 2 倍の大きい方に制限します。これは [RFC6928] の分析および推奨に従うものであり、 TCP の 20 バイトのオーバーヘッドと比較して UDP のオーバーヘッドが 8 バイトと小さいことを考慮するため、バイト制限を増やしています。

最大データグラムサイズがコネクション中に変化した場合、初期 輻輳ウィンドウは新しいサイズで再計算される SHOULD。ハンドシェイクを完了するために 最大データグラムサイズが減少した場合、 輻輳ウィンドウは新しい初期輻輳 ウィンドウに設定される SHOULD

クライアントのアドレスを検証する前は、サーバーは Section 8.1 of [QUIC-TRANSPORT] で規定される増幅防止制限によって さらに制限される可能性があります。 増幅防止制限は輻輳ウィンドウが十分に利用されることを妨げ、 したがって輻輳ウィンドウの増加を遅くする可能性がありますが、 輻輳ウィンドウに直接影響するものではありません。

最小輻輳ウィンドウは、損失、ピアが報告する ECN-CE カウントの増加、 または持続的輻輳への応答として、輻輳ウィンドウが到達し得る 最小値です。RECOMMENDED 値は 2 * max_datagram_size です。

7.3. 輻輳制御 状態

この文書で説明する NewReno 輻輳制御器には、 Figure 1 に示すように 3 つの明確な状態があります。

                 New path or      +------------+
            persistent congestion |   Slow     |
        (O)---------------------->|   Start    |
                                  +------------+
                                        |
                                Loss or |
                        ECN-CE increase |
                                        v
 +------------+     Loss or       +------------+
 | Congestion |  ECN-CE increase  |  Recovery  |
 | Avoidance  |------------------>|   Period   |
 +------------+                   +------------+
           ^                            |
           |                            |
           +----------------------------+
              Acknowledgment of packet
                sent during recovery
Figure 1: 輻輳制御状態 および遷移

これらの状態およびそれらの間の遷移は、後続の 節で説明します。

7.3.1. スロー・スタート

NewReno 送信者は、輻輳ウィンドウが スロースタートしきい値を下回っている間は常にスロースタート状態にあります。 送信者は、スロースタートしきい値が無限大の値に初期化されるため、 スロースタートで開始します。

送信者がスロースタート中である間、各確認応答が処理されるたびに、 確認応答されたバイト数だけ輻輳ウィンドウが増加します。 これにより、輻輳ウィンドウは指数的に増加します。

送信者は、パケットが失われたとき、またはピアが報告する ECN-CE カウントが増加したときに、スロースタートを終了し、 回復期間に入らなければなりません(MUST)。

送信者は、輻輳ウィンドウがスロースタートしきい値より小さい場合には 常にスロースタートに再び入ります。これは持続的輻輳が 宣言された後にのみ発生します。

7.3.2. 回復

NewReno 送信者は、パケットの損失を検出したとき、または ピアが報告する ECN-CE カウントが増加したときに回復期間に入ります。 すでに回復期間中の送信者はその状態にとどまり、 再び入り直すことはありません。

回復期間に入るとき、送信者は、損失が検出されたときの 輻輳ウィンドウ値の半分にスロースタートしきい値を 設定しなければなりません(MUST)。輻輳 ウィンドウは、回復期間を終了する前に、スロースタートしきい値の 減少後の値に設定されなければなりません(MUST)。

実装は、回復期間に入った直後に輻輳 ウィンドウを減少させても MAY、または Proportional Rate Reduction [PRR] などの他のメカニズムを使用して、輻輳ウィンドウを より段階的に減少させてもかまいません。輻輳ウィンドウが直ちに減少される場合、 減少の前に 1 つのパケットを送信できます。これは、失われたパケット内のデータが 再送信される場合に損失回復を高速化し、Section 5 of [RFC6675] で説明される TCP と同様です。

回復期間は、輻輳ウィンドウの減少をラウンド トリップごとに 1 回に制限することを目的としています。したがって、回復期間中、 輻輳ウィンドウは、新たな損失や ECN-CE カウントの増加に応じて 変化しません。

回復期間中に送信されたパケットが確認応答されると、回復期間は終了し、 送信者は輻輳回避に入ります。これは、 回復を開始した失われたセグメントが確認応答されたときに回復が終了するという TCP の回復の定義とは少し異なります [RFC5681]

7.3.3. 輻輳 回避

NewReno 送信者は、輻輳ウィンドウが スロースタートしきい値以上であり、かつ回復期間中でない場合には 常に輻輳回避状態にあります。

輻輳回避中の送信者は、Additive Increase Multiplicative Decrease(AIMD)方式を使用します。この方式では、 1 つの輻輳ウィンドウ分が確認応答されるごとに、輻輳ウィンドウの増加を 最大で 1 つの最大データグラムサイズに制限しなければなりません (MUST)。

送信者は、パケットが失われたとき、またはピアが報告する ECN-CE カウントが増加したときに、輻輳回避を終了して 回復期間に入ります。

7.4. 復号不能パケットの 損失の無視

ハンドシェイク中、パケットが到着したときに一部のパケット保護鍵が 利用できないことがあり、受信者はそのパケットを破棄することを選択できます。 特に、Handshake および 0-RTT パケットは Initial パケットが 到着するまで処理できず、1-RTT パケットはハンドシェイクが完了するまで 処理できません。エンドポイントは、ピアがそれらのパケットを処理するための パケット保護鍵を持つ前に到着した可能性がある Handshake、0-RTT、および 1-RTT パケットの 損失を無視しても MAY。 エンドポイントは、あるパケット番号空間で最も早く確認応答されたパケットより後に 送信されたパケットの損失を無視しては MUST NOT

7.5. プローブタイムアウト

プローブパケットは輻輳制御器によってブロックされては MUST NOT。ただし、送信者は これらのパケットを追加で転送中であるものとして算入しなければなりません (MUST)。これは、これらのパケットがパケット損失を確立することなく ネットワーク負荷を追加するためです。プローブ パケットの送信により、パケットの損失または配送を確立する確認応答を受信するまで、 送信者の bytes in flight が輻輳ウィンドウを超える可能性があることに注意してください。

7.6. 持続的輻輳

送信者が、十分に長い期間にわたって送信されたすべてのパケットの 損失を確立した場合、ネットワークは持続的輻輳を経験していると みなされます。

7.6.1. 継続時間

持続的輻輳の継続時間は次のように計算されます:

(smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay) *
    kPersistentCongestionThreshold

Section 6.2 の PTO 計算とは異なり、この継続時間には、 損失が確立されたパケット番号空間に関係なく max_ack_delay が含まれます。

この継続時間により、送信者は持続的輻輳を確立する前に、 PTO 満了への応答として送信されるものを含め、 TCP が Tail Loss Probes [RFC8985] および RTO [RFC5681] で行うのと同じだけ多くのパケットを送信できます。

kPersistentCongestionThreshold の値が大きいほど、送信者は ネットワーク内の持続的輻輳に対して反応しにくくなり、輻輳した ネットワークへ積極的に送信する結果になる可能性があります。値が小さすぎると、 送信者が不要に持続的輻輳を宣言し、 送信者のスループットが低下する可能性があります。

kPersistentCongestionThreshold の RECOMMENDED 値は 3 であり、これは TCP 送信者が 2 つの TLP の後に RTO を宣言する場合と ほぼ同等の動作になります。

この設計では、連続する PTO イベントを使用して持続的 輻輳を確立しません。これは、アプリケーションのパターンが PTO 満了に影響するためです。 たとえば、少量のデータを送信し、その間に無音期間がある送信者は、 送信するたびに PTO タイマーを再起動し、確認応答を受信していない場合でも PTO タイマーが長期間満了しないようにする可能性があります。 継続時間を使用することで、送信者は PTO 満了に依存せずに 持続的輻輳を確立できます。

7.6.2. 持続的 輻輳の確立

送信者は、確認応答の受信後、ACK 誘発である 2 つのパケットが 損失と宣言され、かつ次の条件を満たす場合に、持続的輻輳を確立します:

  • すべてのパケット番号空間にわたって、これら 2 つのパケットの 送信時刻の間に送信されたパケットが 1 つも確認応答されていないこと。
  • これら 2 つのパケットの送信時刻の間の継続時間が 持続的輻輳の継続時間(Section 7.6.1)を超えていること。そして
  • これら 2 つのパケットが送信されたときに、以前の RTT サンプルが存在していたこと。

これら 2 つのパケットは ACK 誘発でなければなりません (MUST)。これは、受信者が最大確認応答遅延内に ACK 誘発パケットのみを確認応答することを要求されているためです。 Section 13.2 of [QUIC-TRANSPORT] を参照してください。

持続的輻輳期間は、少なくとも 1 つの RTT サンプルが存在するまで開始すべきではありません (SHOULD NOT)。最初の RTT サンプルの前では、送信者は 初期 RTT(Section 6.2.2)に基づいて PTO タイマーをセットしますが、これは実際の RTT より大幅に大きい可能性があります。 以前の RTT サンプルを要求することで、送信者が少なすぎるプローブで 持続的輻輳を確立することを防ぎます。

ネットワーク輻輳はパケット番号空間の影響を受けないため、 持続的 輻輳は、パケット番号空間をまたいで送信されたパケットを考慮する SHOULD。 すべてのパケット番号空間の状態を持たない送信者、または パケット番号空間をまたいで送信時刻を比較できない実装は、 確認応答されたパケット番号空間のみの状態を使用しても MAY。これは持続的輻輳を誤って 宣言する結果になる可能性がありますが、持続的輻輳の検出失敗には つながりません。

持続的輻輳が宣言された場合、送信者の輻輳 ウィンドウは、TCP 送信者の RTO 時の応答 [RFC5681] と同様に、 最小輻輳ウィンドウ(kMinimumWindow)まで減少されなければなりません (MUST)。

7.6.3.

次の例は、送信者が持続的 輻輳をどのように確立し得るかを示します。次を仮定します:

smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay = 2
kPersistentCongestionThreshold = 3

次の一連のイベントを考えます:

Table 1
時刻 動作
t=0 パケット #1 を送信(アプリケーションデータ)
t=1 パケット #2 を送信(アプリケーションデータ)
t=1.2 #1 の確認応答を受信
t=2 パケット #3 を送信(アプリケーションデータ)
t=3 パケット #4 を送信(アプリケーションデータ)
t=4 パケット #5 を送信(アプリケーションデータ)
t=5 パケット #6 を送信(アプリケーションデータ)
t=6 パケット #7 を送信(アプリケーションデータ)
t=8 パケット #8 を送信(PTO 1)
t=12 パケット #9 を送信(PTO 2)
t=12.2 #9 の確認応答を受信

パケット 9 の確認応答が t = 12.2 で受信されると、パケット 2 から 8 までが 損失と宣言されます。

輻輳期間は、最も古い 損失パケットと最も新しい損失パケットの間の時間として計算されます: 8 - 1 = 7。持続的輻輳の継続時間は 2 * 3 = 6 です。 しきい値に達しており、かつ最も古い損失パケットと最も新しい損失パケットの間の パケットが 1 つも確認応答されていなかったため、ネットワークは 持続的輻輳を経験したとみなされます。

この例では PTO 満了を示していますが、持続的 輻輳を確立するために PTO 満了は必須ではありません。

7.7. ペーシング

送信者は、輻輳制御器からの入力に基づいて、すべての転送中 パケットの送信をペーシングする SHOULD

複数のパケットを間に遅延なくネットワークへ送信すると、 短期的な輻輳や損失を引き起こす可能性のあるパケットバーストが発生します。 送信者は、ペーシングを使用するか、そのようなバーストを制限しなければなりません (MUST)。 送信者はバーストを初期輻輳ウィンドウに制限する SHOULDSection 7.2 を参照してください。 受信者へのネットワークパスがより大きなバーストを吸収できることを知っている送信者は、 より高い制限を使用しても MAY

実装は、輻輳制御器がペーサーとうまく連携するように 設計するよう注意すべきです。たとえば、ペーサーが輻輳 制御器をラップして輻輳ウィンドウの利用可能性を制御することもできますし、 ペーサーが輻輳制御器から渡されたパケットをペーシングして送出することもできます。

ACK フレームの適時な配送は、効率的な損失回復にとって重要です。 ピアへの配送を遅延させないため、 ACK フレームのみを含むパケットは、したがってペーシングされない SHOULD

エンドポイントは、選択した方法でペーシングを実装できます。 完全にペーシングされた送信者は、パケットを時間全体に正確に均等に 分散します。この文書のもののようなウィンドウベースの輻輳制御器では、 そのレートは、RTT にわたって輻輳ウィンドウを平均することで 計算できます。congestion_window がバイト単位である場合、 バイト毎時間の単位で表されるレートは次のとおりです:

rate = N * congestion_window / smoothed_rtt

または、時間単位のパケット間隔として表すと次のとおりです:

interval = ( smoothed_rtt * packet_size / congestion_window ) / N

N に小さいが少なくとも 1 の値(たとえば 1.25)を使用すると、 RTT の変動によって輻輳ウィンドウが利用不足にならないことを 保証できます。

パケット化、スケジューリング遅延、計算効率などの 実際上の考慮事項により、送信者は RTT よりはるかに短い時間 期間にわたってこのレートから逸脱することがあります。

ペーシングの実装戦略の 1 つとして、リーキーバケット アルゴリズムを使用する方法があります。この場合、「バケット」の容量は最大バーストサイズに制限され、 「バケット」が満たされるレートは上記の関数によって決定されます。

7.8. 輻輳ウィンドウの 利用不足

bytes in flight が輻輳ウィンドウより小さく、送信が ペーシングによって制限されていない場合、輻輳ウィンドウは利用不足です。 これは、アプリケーションデータの不足やフロー制御制限によって発生することがあります。 これが発生した場合、輻輳ウィンドウはスロースタートまたは 輻輳回避のいずれにおいても増加されるべきではありません (SHOULD NOT)。

パケットをペーシングする送信者(Section 7.7 を参照)は、 この遅延によりパケット送信を遅らせ、輻輳ウィンドウを十分に 利用しないことがあります。送信者は、ペーシング遅延がなければ輻輳ウィンドウを十分に 利用していたはずである場合、自身をアプリケーション制限状態とみなすべきではありません (SHOULD NOT)。

送信者は、利用不足の期間の後に輻輳ウィンドウを更新するため、 [RFC7661] で TCP 向けに提案されているものなど、 代替メカニズムを実装しても MAY

8. セキュリティ上の考慮事項

8.1. 損失および輻輳 シグナル

損失検出および輻輳制御は、基本的に、認証されていない エンティティからの遅延、損失、ECN マーキングなどのシグナルを 消費することを伴います。攻撃者は、パケットをドロップする、パス遅延を 戦略的に変更する、または ECN コードポイントを変更することで、 これらのシグナルを操作し、エンドポイントの送信レートを低下させることができます。

8.2. トラフィック分析

ACK フレームのみを運ぶパケットは、パケットサイズを観測することで ヒューリスティックに識別できます。確認応答パターンは、リンク 特性またはアプリケーションの挙動に関する情報を露出させる可能性があります。 漏えいする情報を減らすため、エンドポイントは確認応答を他のフレームと 束ねることができます。または、性能への潜在的なコストを伴って PADDING フレームを使用できます。

8.3. ECN マーキングの誤報告

受信者は、送信者の輻輳応答を変更するために ECN マーキングを誤って報告できます。ECN-CE マーキングの報告を抑制すると、 送信者の送信レートが増加する可能性があります。この増加は 輻輳および損失を引き起こす可能性があります。

送信者は、送信する時折のパケットに ECN-CE マーキングを付けることで、 報告の抑制を検出できます。ECN-CE マーキング付きで送信されたパケットが 確認応答されたときに CE マーク付きであったと報告されない場合、 送信者は、そのパス上で後続に送信されるパケットに ECN-Capable Transport(ECT) コードポイントを設定しないことで、そのパスの ECN を無効にできます [RFC3168]

追加の ECN-CE マーキングを報告すると、送信者は 送信レートを低下させます。これは、接続のフロー制御 制限を低く通知することと効果が似ているため、そうすることによって 利点は得られません。

エンドポイントは、使用する輻輳制御器を選択します。輻輳 制御器は ECN-CE の報告に対してレートを下げることで応答しますが、 その応答は異なる場合があります。マーキングは損失と同等に扱うことができます [RFC3168] が、[RFC8511] または [RFC8311] のように、 他の応答を規定することもできます。

9. 参考文献

9.1. 規範的参考文献

[QUIC-TLS]
Thomson, M., Ed. and S. Turner, Ed., "TLS を使用した QUIC の保護", RFC 9001, DOI 10.17487/RFC9001, , <https://www.rfc-editor.org/info/rfc9001>.
[QUIC-TRANSPORT]
Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: UDP ベースの多重化され安全なトランスポート", RFC 9000, DOI 10.17487/RFC9000, , <https://www.rfc-editor.org/info/rfc9000>.
[RFC2119]
Bradner, S., "要求レベルを示すために RFC で使用するキーワード", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3168]
Ramakrishnan, K., Floyd, S., and D. Black, "IP への Explicit Congestion Notification(ECN)の追加", RFC 3168, DOI 10.17487/RFC3168, , <https://www.rfc-editor.org/info/rfc3168>.
[RFC8085]
Eggert, L., Fairhurst, G., and G. Shepherd, "UDP 使用ガイドライン", BCP 145, RFC 8085, DOI 10.17487/RFC8085, , <https://www.rfc-editor.org/info/rfc8085>.
[RFC8174]
Leiba, B., "RFC 2119 キーワードにおける 大文字と小文字の曖昧性", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.

9.2. 参考情報の参考文献

[FACK]
Mathis, M. and J. Mahdavi, "Forward acknowledgement: TCP 輻輳制御の改良", ACM SIGCOMM Computer Communication Review, DOI 10.1145/248157.248181, , <https://doi.org/10.1145/248157.248181>.
[PRR]
Mathis, M., Dukkipati, N., and Y. Cheng, "TCP のための Proportional Rate Reduction", RFC 6937, DOI 10.17487/RFC6937, , <https://www.rfc-editor.org/info/rfc6937>.
[RETRANSMISSION]
Karn, P. and C. Partridge, "信頼性のあるトランスポートプロトコルにおけるラウンドトリップ時間推定の改善", ACM Transactions on Computer Systems, DOI 10.1145/118544.118549, , <https://doi.org/10.1145/118544.118549>.
[RFC2018]
Mathis, M., Mahdavi, J., Floyd, S., and A. Romanow, "TCP Selective Acknowledgment Options", RFC 2018, DOI 10.17487/RFC2018, , <https://www.rfc-editor.org/info/rfc2018>.
[RFC3465]
Allman, M., "Appropriate Byte Counting(ABC)による TCP 輻輳制御", RFC 3465, DOI 10.17487/RFC3465, , <https://www.rfc-editor.org/info/rfc3465>.
[RFC5681]
Allman, M., Paxson, V., and E. Blanton, "TCP 輻輳制御", RFC 5681, DOI 10.17487/RFC5681, , <https://www.rfc-editor.org/info/rfc5681>.
[RFC5682]
Sarolahti, P., Kojo, M., Yamamoto, K., and M. Hata, "Forward RTO-Recovery(F-RTO): TCP における偽の 再送信タイムアウトを検出するためのアルゴリズム", RFC 5682, DOI 10.17487/RFC5682, , <https://www.rfc-editor.org/info/rfc5682>.
[RFC5827]
Allman, M., Avrachenkov, K., Ayesta, U., Blanton, J., and P. Hurtig, "TCP および Stream Control Transmission Protocol(SCTP)のための Early Retransmit", RFC 5827, DOI 10.17487/RFC5827, , <https://www.rfc-editor.org/info/rfc5827>.
[RFC6298]
Paxson, V., Allman, M., Chu, J., and M. Sargent, "TCP の再送信タイマーの計算", RFC 6298, DOI 10.17487/RFC6298, , <https://www.rfc-editor.org/info/rfc6298>.
[RFC6582]
Henderson, T., Floyd, S., Gurtov, A., and Y. Nishida, "TCP の Fast Recovery アルゴリズムに対する NewReno 修正", RFC 6582, DOI 10.17487/RFC6582, , <https://www.rfc-editor.org/info/rfc6582>.
[RFC6675]
Blanton, E., Allman, M., Wang, L., Jarvinen, I., Kojo, M., and Y. Nishida, "TCP の Selective Acknowledgment (SACK)に基づく保守的な損失回復アルゴリズム", RFC 6675, DOI 10.17487/RFC6675, , <https://www.rfc-editor.org/info/rfc6675>.
[RFC6928]
Chu, J., Dukkipati, N., Cheng, Y., and M. Mathis, "TCP の初期ウィンドウの増加", RFC 6928, DOI 10.17487/RFC6928, , <https://www.rfc-editor.org/info/rfc6928>.
[RFC7661]
Fairhurst, G., Sathiaseelan, A., and R. Secchi, "レート制限されたトラフィックをサポートするための TCP の更新", RFC 7661, DOI 10.17487/RFC7661, , <https://www.rfc-editor.org/info/rfc7661>.
[RFC8311]
Black, D., "Explicit Congestion Notification(ECN)実験に関する制限の緩和", RFC 8311, DOI 10.17487/RFC8311, , <https://www.rfc-editor.org/info/rfc8311>.
[RFC8312]
Rhee, I., Xu, L., Ha, S., Zimmermann, A., Eggert, L., and R. Scheffenegger, "高速長距離ネットワークのための CUBIC", RFC 8312, DOI 10.17487/RFC8312, , <https://www.rfc-editor.org/info/rfc8312>.
[RFC8511]
Khademi, N., Welzl, M., Armitage, G., and G. Fairhurst, "ECN による TCP Alternative Backoff(ABE)", RFC 8511, DOI 10.17487/RFC8511, , <https://www.rfc-editor.org/info/rfc8511>.
[RFC8985]
Cheng, Y., Cardwell, N., Dukkipati, N., and P. Jha, "TCP のための RACK-TLP 損失検出アルゴリズム", RFC 8985, DOI 10.17487/RFC8985, , <https://www.rfc-editor.org/info/rfc8985>.

付録 A. 損失回復の擬似コード

ここでは、第 6 節で説明した損失検出メカニズムの 実装例について説明します。

この節の擬似コード片はコードコンポーネントとしてライセンスされています。著作権 表示を参照してください。

A.1. 送信済みパケットの追跡

輻輳制御を正しく実装するため、QUIC 送信者はすべての ACK 誘発パケットを、そのパケットが確認応答されるか損失するまで追跡します。 実装は、パケット番号および暗号コンテキストによってこの情報にアクセスし、 損失回復および輻輳制御のためにパケットごとのフィールド (付録 A.1.1)を保存できることが期待されます。

パケットが損失と宣言された後でも、エンドポイントはパケットの並べ替えを 許容するため、一定時間その状態を維持できます。第 13.3 節 of [QUIC-TRANSPORT]を参照してください。 これにより、送信者は偽の再送信を検出できます。

送信済みパケットはパケット番号空間ごとに追跡され、ACK 処理は単一の空間にのみ適用されます。

A.1.1. 送信済みパケットのフィールド

packet_number:

送信済みパケットのパケット番号。

ack_eliciting:

パケットが ACK 誘発であるかどうかを示す Boolean。 true の場合、ピアがその ACK を含む ACK フレームの送信を max_ack_delay まで遅らせる可能性はありますが、確認応答が受信されることが 期待されます。

in_flight:

そのパケットが bytes in flight に算入されるかどうかを示す Boolean。

sent_bytes:

パケット内で送信されたバイト数。UDP または IP オーバーヘッドは含まず、QUIC フレーミングオーバーヘッドは含みます。

time_sent:

パケットが送信された時刻。

A.2. 関連する定数

損失回復で使用される定数は、RFC、論文、および 一般的な実践の組み合わせに基づいています。

kPacketThreshold:

パケットしきい値による損失検出がパケットを損失とみなす前の、 パケット単位での最大並べ替え量。第 6.1.1 節で推奨される値は 3 です。

kTimeThreshold:

時間しきい値による損失検出がパケットを損失とみなす前の、 時間単位での最大並べ替え量。RTT 乗数として指定されます。第 6.1.2 節で推奨される値は 9/8 です。

kGranularity:

タイマー粒度。これはシステム依存の値であり、第 6.1.2 節では 1 ms の値を推奨しています。

kInitialRtt:

RTT サンプルが取得される前に使用される RTT。第 6.2.2 節で推奨される値は 333 ms です。

kPacketNumberSpace:

3 つのパケット番号空間を列挙する enum:

enum kPacketNumberSpace {
  Initial,
  Handshake,
  ApplicationData,
}

A.3. 関連する変数

輻輳制御メカニズムを実装するために必要な変数について、 この節で説明します。

latest_rtt:

以前に未確認応答だったパケットに対する確認応答を受信したときに行われた、 最新の RTT 測定値。

smoothed_rtt:

第 5.3 節で説明されているように 計算される、コネクションの平滑化 RTT。

rttvar:

第 5.3 節で説明されているように計算される RTT 変動。

min_rtt:

第 5.2 節で説明されているように、 確認応答遅延を無視して一定期間に観測された最小 RTT。

first_rtt_sample:

最初の RTT サンプルが取得された時刻。

max_ack_delay:

受信者が Application Data パケット番号空間のパケットに対する 確認応答を遅延させる意図のある最大時間。これは同名のトランスポートパラメーター (第 18.2 節 of [QUIC-TRANSPORT])によって定義されます。受信した ACK フレーム内の実際の ack_delay は、遅延したタイマー、並べ替え、または損失により さらに大きくなる可能性があることに注意してください。

loss_detection_timer:

損失検出に使用されるマルチモーダルタイマー。

pto_count:

確認応答を受信せずに PTO が送信された回数。

time_of_last_ack_eliciting_packet[kPacketNumberSpace]:

最新の ACK 誘発パケットが送信された時刻。

largest_acked_packet[kPacketNumberSpace]:

そのパケット番号空間でこれまでに確認応答された最大のパケット番号。

loss_time[kPacketNumberSpace]:

そのパケット番号空間の次のパケットが、時間上の並べ替えウィンドウを 超過したことに基づいて損失とみなされ得る時刻。

sent_packets[kPacketNumberSpace]:

パケット番号空間内のパケット番号から、それらに関する情報への関連付け。 上記の付録 A.1で詳細に説明されています。

A.4. 初期化

コネクションの開始時に、損失検出変数を次のように初期化します:

loss_detection_timer.reset()
pto_count = 0
latest_rtt = 0
smoothed_rtt = kInitialRtt
rttvar = kInitialRtt / 2
min_rtt = 0
first_rtt_sample = 0
for pn_space in [ Initial, Handshake, ApplicationData ]:
  largest_acked_packet[pn_space] = infinite
  time_of_last_ack_eliciting_packet[pn_space] = 0
  loss_time[pn_space] = 0

A.5. パケット送信時

パケットが送信された後、そのパケットに関する情報が保存されます。 OnPacketSent へのパラメーターは、上記の付録 A.1.1で詳細に説明されています。

OnPacketSent の擬似コードは次のとおりです:

OnPacketSent(packet_number, pn_space, ack_eliciting,
             in_flight, sent_bytes):
  sent_packets[pn_space][packet_number].packet_number =
                                           packet_number
  sent_packets[pn_space][packet_number].time_sent = now()
  sent_packets[pn_space][packet_number].ack_eliciting =
                                           ack_eliciting
  sent_packets[pn_space][packet_number].in_flight = in_flight
  sent_packets[pn_space][packet_number].sent_bytes = sent_bytes
  if (in_flight):
    if (ack_eliciting):
      time_of_last_ack_eliciting_packet[pn_space] = now()
    OnPacketSentCC(sent_bytes)
    SetLossDetectionTimer()

A.6. データグラム 受信時

サーバーが増幅防止制限によってブロックされている場合、 データグラムを受信すると、そのデータグラム内のパケットが 1 つも 正常に処理されなくても、ブロックが解除されます。このような場合、PTO タイマーを再度セットする必要があります。

OnDatagramReceived の擬似コードは次のとおりです:

OnDatagramReceived(datagram):
  // If this datagram unblocks the server, arm the
  // PTO timer to avoid deadlock.
  if (server was at anti-amplification limit):
    SetLossDetectionTimer()
    if loss_detection_timer.timeout < now():
      // Execute PTO if it would have expired
      // while the amplification limit applied.
      OnLossDetectionTimeout()

A.7. 確認応答 受信時

ACK フレームが受信されると、任意の数のパケットを新たに 確認応答することがあります。

OnAckReceived および UpdateRtt の擬似コードは次のとおりです:

IncludesAckEliciting(packets):
  for packet in packets:
    if (packet.ack_eliciting):
      return true
  return false

OnAckReceived(ack, pn_space):
  if (largest_acked_packet[pn_space] == infinite):
    largest_acked_packet[pn_space] = ack.largest_acked
  else:
    largest_acked_packet[pn_space] =
        max(largest_acked_packet[pn_space], ack.largest_acked)

  // DetectAndRemoveAckedPackets finds packets that are newly
  // acknowledged and removes them from sent_packets.
  newly_acked_packets =
      DetectAndRemoveAckedPackets(ack, pn_space)
  // Nothing to do if there are no newly acked packets.
  if (newly_acked_packets.empty()):
    return

  // Update the RTT if the largest acknowledged is newly acked
  // and at least one ack-eliciting was newly acked.
  if (newly_acked_packets.largest().packet_number ==
          ack.largest_acked &&
      IncludesAckEliciting(newly_acked_packets)):
    latest_rtt =
      now() - newly_acked_packets.largest().time_sent
    UpdateRtt(ack.ack_delay)

  // Process ECN information if present.
  if (ACK frame contains ECN information):
      ProcessECN(ack, pn_space)

  lost_packets = DetectAndRemoveLostPackets(pn_space)
  if (!lost_packets.empty()):
    OnPacketsLost(lost_packets)
  OnPacketsAcked(newly_acked_packets)

  // Reset pto_count unless the client is unsure if
  // the server has validated the client's address.
  if (PeerCompletedAddressValidation()):
    pto_count = 0
  SetLossDetectionTimer()


UpdateRtt(ack_delay):
  if (first_rtt_sample == 0):
    min_rtt = latest_rtt
    smoothed_rtt = latest_rtt
    rttvar = latest_rtt / 2
    first_rtt_sample = now()
    return

  // min_rtt ignores acknowledgment delay.
  min_rtt = min(min_rtt, latest_rtt)
  // Limit ack_delay by max_ack_delay after handshake
  // confirmation.
  if (handshake confirmed):
    ack_delay = min(ack_delay, max_ack_delay)

  // Adjust for acknowledgment delay if plausible.
  adjusted_rtt = latest_rtt
  if (latest_rtt >= min_rtt + ack_delay):
    adjusted_rtt = latest_rtt - ack_delay

  rttvar = 3/4 * rttvar + 1/4 * abs(smoothed_rtt - adjusted_rtt)
  smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt

A.8. 損失検出 タイマーの設定

QUIC の損失検出は、すべてのタイムアウト損失検出に単一のタイマーを使用します。 タイマーの継続時間はタイマーのモードに基づき、そのモードは後述する パケットおよびタイマーイベントで設定されます。以下に定義する関数 SetLossDetectionTimer は、単一のタイマーがどのように設定されるかを示します。

このアルゴリズムでは、特にタイマーの起動が遅れた場合、 タイマーが過去に設定されることがあります。過去に設定されたタイマーは直ちに発火します。

SetLossDetectionTimer の擬似コードは次のとおりです(ここで「^」演算子は べき乗を表します):

GetLossTimeAndSpace():
  time = loss_time[Initial]
  space = Initial
  for pn_space in [ Handshake, ApplicationData ]:
    if (time == 0 || loss_time[pn_space] < time):
      time = loss_time[pn_space];
      space = pn_space
  return time, space

GetPtoTimeAndSpace():
  duration = (smoothed_rtt + max(4 * rttvar, kGranularity))
      * (2 ^ pto_count)
  // Anti-deadlock PTO starts from the current time
  if (no ack-eliciting packets in flight):
    assert(!PeerCompletedAddressValidation())
    if (has handshake keys):
      return (now() + duration), Handshake
    else:
      return (now() + duration), Initial
  pto_timeout = infinite
  pto_space = Initial
  for space in [ Initial, Handshake, ApplicationData ]:
    if (no ack-eliciting packets in flight in space):
        continue;
    if (space == ApplicationData):
      // Skip Application Data until handshake confirmed.
      if (handshake is not confirmed):
        return pto_timeout, pto_space
      // Include max_ack_delay and backoff for Application Data.
      duration += max_ack_delay * (2 ^ pto_count)

    t = time_of_last_ack_eliciting_packet[space] + duration
    if (t < pto_timeout):
      pto_timeout = t
      pto_space = space
  return pto_timeout, pto_space

PeerCompletedAddressValidation():
  // Assume clients validate the server's address implicitly.
  if (endpoint is server):
    return true
  // Servers complete address validation when a
  // protected packet is received.
  return has received Handshake ACK ||
       handshake confirmed

SetLossDetectionTimer():
  earliest_loss_time, _ = GetLossTimeAndSpace()
  if (earliest_loss_time != 0):
    // Time threshold loss detection.
    loss_detection_timer.update(earliest_loss_time)
    return

  if (server is at anti-amplification limit):
    // The server's timer is not set if nothing can be sent.
    loss_detection_timer.cancel()
    return

  if (no ack-eliciting packets in flight &&
      PeerCompletedAddressValidation()):
    // There is nothing to detect lost, so no timer is set.
    // However, the client needs to arm the timer if the
    // server might be blocked by the anti-amplification limit.
    loss_detection_timer.cancel()
    return

  timeout, _ = GetPtoTimeAndSpace()
  loss_detection_timer.update(timeout)

A.9. タイムアウト時

損失検出タイマーが満了すると、タイマーのモードによって 実行される動作が決まります。

OnLossDetectionTimeout の擬似コードは次のとおりです:

OnLossDetectionTimeout():
  earliest_loss_time, pn_space = GetLossTimeAndSpace()
  if (earliest_loss_time != 0):
    // Time threshold loss Detection
    lost_packets = DetectAndRemoveLostPackets(pn_space)
    assert(!lost_packets.empty())
    OnPacketsLost(lost_packets)
    SetLossDetectionTimer()
    return

  if (no ack-eliciting packets in flight):
    assert(!PeerCompletedAddressValidation())
    // Client sends an anti-deadlock packet: Initial is padded
    // to earn more anti-amplification credit,
    // a Handshake packet proves address ownership.
    if (has Handshake keys):
      SendOneAckElicitingHandshakePacket()
    else:
      SendOneAckElicitingPaddedInitialPacket()
  else:
    // PTO. Send new data if available, else retransmit old data.
    // If neither is available, send a single PING frame.
    _, pn_space = GetPtoTimeAndSpace()
    SendOneOrTwoAckElicitingPackets(pn_space)

  pto_count++
  SetLossDetectionTimer()

A.10. 失われたパケットの検出

DetectAndRemoveLostPackets は、ACK を受信するたび、または時間 しきい値損失検出タイマーが満了するたびに呼び出されます。この関数は、 そのパケット番号空間の sent_packets に対して動作し、新たに 損失として検出されたパケットのリストを返します。

DetectAndRemoveLostPackets の擬似コードは次のとおりです:

DetectAndRemoveLostPackets(pn_space):
  assert(largest_acked_packet[pn_space] != infinite)
  loss_time[pn_space] = 0
  lost_packets = []
  loss_delay = kTimeThreshold * max(latest_rtt, smoothed_rtt)

  // Minimum time of kGranularity before packets are deemed lost.
  loss_delay = max(loss_delay, kGranularity)

  // Packets sent before this time are deemed lost.
  lost_send_time = now() - loss_delay

  foreach unacked in sent_packets[pn_space]:
    if (unacked.packet_number > largest_acked_packet[pn_space]):
      continue

    // Mark packet as lost, or set time when it should be marked.
    // Note: The use of kPacketThreshold here assumes that there
    // were no sender-induced gaps in the packet number space.
    if (unacked.time_sent <= lost_send_time ||
        largest_acked_packet[pn_space] >=
          unacked.packet_number + kPacketThreshold):
      sent_packets[pn_space].remove(unacked.packet_number)
      lost_packets.insert(unacked)
    else:
      if (loss_time[pn_space] == 0):
        loss_time[pn_space] = unacked.time_sent + loss_delay
      else:
        loss_time[pn_space] = min(loss_time[pn_space],
                                  unacked.time_sent + loss_delay)
  return lost_packets

A.11. Initial 鍵または Handshake 鍵の破棄時

Initial 鍵または Handshake 鍵が破棄されると、その空間のパケットは 破棄され、損失検出状態が更新されます。

OnPacketNumberSpaceDiscarded の擬似コードは次のとおりです:

OnPacketNumberSpaceDiscarded(pn_space):
  assert(pn_space != ApplicationData)
  RemoveFromBytesInFlight(sent_packets[pn_space])
  sent_packets[pn_space].clear()
  // Reset the loss detection and PTO timer
  time_of_last_ack_eliciting_packet[pn_space] = 0
  loss_time[pn_space] = 0
  pto_count = 0
  SetLossDetectionTimer()

付録 B. 輻輳制御 擬似コード

ここでは、第 7 節で説明した 輻輳制御器の実装例について説明します。

この節の擬似コード片はコードコンポーネントとしてライセンスされています。著作権 表示を参照してください。

B.1. 関連する定数

輻輳制御で使用される定数は、RFC、 論文、および一般的な実践の組み合わせに基づいています。

kInitialWindow:

第 7.2 節で説明されている、初期 bytes in flight の デフォルト制限。

kMinimumWindow:

第 7.2 節で説明されている、バイト単位の最小輻輳ウィンドウ。

kLossReductionFactor:

新たな損失イベントが検出されたときに輻輳ウィンドウを減らすために 適用されるスケーリング係数。第 7 節では 0.5 の値を推奨しています。

kPersistentCongestionThreshold:

持続的輻輳が確立されるための期間。 PTO 乗数として指定されます。第 7.6 節では 3 の値を推奨しています。

B.2. 関連する変数

輻輳制御メカニズムを実装するために必要な変数について、 この節で説明します。

max_datagram_size:

送信者の現在の最大ペイロードサイズ。UDP または IP オーバーヘッドは含みません。最大データグラムサイズは、輻輳ウィンドウ 計算に使用されます。エンドポイントは、Path Maximum Transmission Unit (PMTU。第 14.2 節 of [QUIC-TRANSPORT]を参照)に基づいて この変数の値を設定し、最小値は 1200 バイトです。

ecn_ce_counters[kPacketNumberSpace]:

ACK フレーム内でピアによってパケット番号空間について報告された ECN-CE カウンターの最高値。この値は、報告された ECN-CE カウンターの 増加を検出するために使用されます。

bytes_in_flight:

少なくとも 1 つの ACK 誘発フレームまたは PADDING フレームを含み、 まだ確認応答されておらず、損失とも宣言されていない、すべての送信済みパケットの バイト単位のサイズの合計。このサイズには IP または UDP オーバーヘッドは含まれませんが、 QUIC ヘッダーおよび Authenticated Encryption with Associated Data (AEAD)オーバーヘッドは含まれます。ACK フレームのみを含むパケットは、 輻輳制御が輻輳フィードバックを妨げないように、bytes_in_flight に 算入されません。

congestion_window:

転送中であることが許可される最大バイト数。

congestion_recovery_start_time:

損失または ECN の検出により現在の回復期間が開始した時刻。 この時刻の後に送信されたパケットが確認応答されると、QUIC は 輻輳回復を終了します。

ssthresh:

バイト単位のスロースタートしきい値。輻輳ウィンドウが ssthresh を下回る場合、モードはスロースタートであり、ウィンドウは 確認応答されたバイト数だけ増加します。

輻輳制御擬似コードは、損失回復擬似コードの一部の変数にも アクセスします。

B.3. 初期化

コネクションの開始時に、輻輳制御変数を 次のように初期化します:

congestion_window = kInitialWindow
bytes_in_flight = 0
congestion_recovery_start_time = 0
ssthresh = infinite
for pn_space in [ Initial, Handshake, ApplicationData ]:
  ecn_ce_counters[pn_space] = 0

B.4. パケット送信時

パケットが送信され、それが非 ACK フレームを含むたびに、そのパケットは bytes_in_flight を増加させます。

OnPacketSentCC(sent_bytes):
  bytes_in_flight += sent_bytes

B.5. パケット 確認応答時

これは、損失検出の OnAckReceived から呼び出され、 sent_packets からの newly acked_packets が渡されます。

輻輳回避において、congestion_window に整数表現を使用する実装者は 除算に注意すべきであり、第 2.1 節 of [RFC3465]で提案されている 代替手法を使用できます。

InCongestionRecovery(sent_time):
  return sent_time <= congestion_recovery_start_time

OnPacketsAcked(acked_packets):
  for acked_packet in acked_packets:
    OnPacketAcked(acked_packet)

OnPacketAcked(acked_packet):
  if (!acked_packet.in_flight):
    return;
  // Remove from bytes_in_flight.
  bytes_in_flight -= acked_packet.sent_bytes
  // Do not increase congestion_window if application
  // limited or flow control limited.
  if (IsAppOrFlowControlLimited())
    return
  // Do not increase congestion window in recovery period.
  if (InCongestionRecovery(acked_packet.time_sent)):
    return
  if (congestion_window < ssthresh):
    // Slow start.
    congestion_window += acked_packet.sent_bytes
  else:
    // Congestion avoidance.
    congestion_window +=
      max_datagram_size * acked_packet.sent_bytes
      / congestion_window

B.6. 新しい輻輳 イベント時

これは、新しい輻輳イベントが検出されたときに、ProcessECN および OnPacketsLost から呼び出されます。まだ回復中でない場合、これは回復期間を開始し、 スロースタートしきい値および輻輳ウィンドウを直ちに減少させます。

OnCongestionEvent(sent_time):
  // No reaction if already in a recovery period.
  if (InCongestionRecovery(sent_time)):
    return

  // Enter recovery period.
  congestion_recovery_start_time = now()
  ssthresh = congestion_window * kLossReductionFactor
  congestion_window = max(ssthresh, kMinimumWindow)
  // A packet can be sent to speed up loss recovery.
  MaybeSendOnePacket()

B.7. ECN 情報の処理

これは、ECN セクションを持つ ACK フレームがピアから受信されたときに 呼び出されます。

ProcessECN(ack, pn_space):
  // If the ECN-CE counter reported by the peer has increased,
  // this could be a new congestion event.
  if (ack.ce_counter > ecn_ce_counters[pn_space]):
    ecn_ce_counters[pn_space] = ack.ce_counter
    sent_time = sent_packets[ack.largest_acked].time_sent
    OnCongestionEvent(sent_time)

B.8. パケット損失時

これは、DetectAndRemoveLostPackets がパケットを損失とみなしたときに呼び出されます。

OnPacketsLost(lost_packets):
  sent_time_of_last_loss = 0
  // Remove lost packets from bytes_in_flight.
  for lost_packet in lost_packets:
    if lost_packet.in_flight:
      bytes_in_flight -= lost_packet.sent_bytes
      sent_time_of_last_loss =
        max(sent_time_of_last_loss, lost_packet.time_sent)
  // Congestion event if in-flight packets were lost
  if (sent_time_of_last_loss != 0):
    OnCongestionEvent(sent_time_of_last_loss)

  // Reset the congestion window if the loss of these
  // packets indicates persistent congestion.
  // Only consider packets sent after getting an RTT sample.
  if (first_rtt_sample == 0):
    return
  pc_lost = []
  for lost in lost_packets:
    if lost.time_sent > first_rtt_sample:
      pc_lost.insert(lost)
  if (InPersistentCongestion(pc_lost)):
    congestion_window = kMinimumWindow
    congestion_recovery_start_time = 0

B.9. 破棄された パケットを Bytes in Flight から削除

Initial 鍵または Handshake 鍵が破棄されると、その空間で送信されたパケットは bytes in flight に算入されなくなります。

RemoveFromBytesInFlight の擬似コードは次のとおりです:

RemoveFromBytesInFlight(discarded_packets):
  // Remove any unacknowledged packets from flight.
  foreach packet in discarded_packets:
    if packet.in_flight
      bytes_in_flight -= size

貢献者

IETF QUIC ワーキンググループは、多くの 人々から非常に大きな支援を受けました。次の人々は、この 文書に実質的な貢献を提供しました:

著者の連絡先

Jana Iyengar (編集者)
Fastly
Ian Swett (編集者)
Google