| RFC 9002 | QUIC 損失検出 | 2021年5月 |
| Iyengar & Swett | 標準化過程 | [ページ] |
この文書は、QUIC の損失検出および輻輳制御メカニズムについて 説明します。¶
これはインターネット標準化過程の文書です。¶
この文書は Internet Engineering Task Force (IETF) の成果物です。これは IETF コミュニティの合意を 表しています。公開レビューを受けており、 Internet Engineering Steering Group (IESG) によって公開が承認されています。 インターネット標準に関する詳細情報は RFC 7841 の第2節で入手できます。¶
この文書の現在の状態、正誤表、および フィードバックを提供する方法に関する情報は、 https://www.rfc-editor.org/info/rfc9002 で入手できます。¶
Copyright (c) 2021 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.¶
QUIC は、安全な汎用トランスポートプロトコルであり、 [QUIC-TRANSPORT] で説明されています。この文書は、QUIC の損失 検出および輻輳 制御メカニズムについて説明します。¶
この文書におけるキーワード「MUST」、「MUST NOT」、 「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、 「SHOULD NOT」、「RECOMMENDED」、「NOT RECOMMENDED」、「MAY」、および「OPTIONAL」は、 ここに示すようにすべて大文字で現れる場合に限り、BCP 14 [RFC2119] [RFC8174] で説明されているとおりに解釈されます。¶
この文書で使用される用語の定義:¶
QUIC のすべての送信はパケットレベルのヘッダー付きで送られます。このヘッダーは 暗号化レベルを示し、パケットシーケンス番号(以下では パケット番号と呼ぶ)を含みます。暗号化レベルは、 Section 12.3 of [QUIC-TRANSPORT] で説明されているように、パケット番号空間を示します。パケット番号は コネクションの存続期間中、1 つのパケット番号空間内では決して 繰り返されません。パケット番号は空間内で単調増加順に送信され、 曖昧さを防ぎます。一部のパケット番号が使用されず、意図的な ギャップを残すことは許可されています。¶
この設計により、送信と再送信を区別する必要がなくなります。 これにより、TCP の損失検出メカニズムを QUIC が解釈する際の 大きな複雑さが取り除かれます。¶
QUIC パケットは、異なる型の複数のフレームを含むことができます。回復 メカニズムは、信頼性のある配送が必要なデータおよびフレームが 確認応答されるか、損失と宣言され、必要に応じて新しいパケットで 送信されることを保証します。パケットに含まれるフレームの型は、 回復および輻輳制御ロジックに影響します:¶
TCP の損失検出および輻輳制御に詳しい読者は、 ここに、よく知られた TCP のものと並行するアルゴリズムを見つけるでしょう。 しかし、QUIC と TCP のプロトコル上の違いは、アルゴリズム上の違いに つながります。これらのプロトコル上の違いを以下に簡潔に説明します。¶
QUIC は各暗号化レベルに対して個別のパケット番号空間を使用します。 ただし、0-RTT とすべての世代の 1-RTT 鍵は同じパケット 番号空間を使用します。個別のパケット番号空間により、 ある暗号化レベルで送信されたパケットの確認応答が、 別の暗号化レベルで送信されたパケットの 偽の再送信を引き起こさないことが保証されます。輻輳制御と ラウンドトリップ時間(RTT) 測定は、パケット番号空間をまたいで統合されます。¶
TCP は送信者での送信順序と受信者での配送順序を混同しており、 その結果、再送信の曖昧性問題 [RETRANSMISSION] が生じます。QUIC は 送信順序を配送順序から分離します。 パケット番号は送信順序を示し、配送順序は STREAM フレーム内の ストリームオフセットによって決定されます。¶
QUIC のパケット番号は、パケット番号空間内で厳密に増加し、 送信順序を直接符号化します。より大きなパケット番号は、 そのパケットが後で送信されたことを意味し、より小さなパケット番号は、 そのパケットが先に送信されたことを意味します。ACK 誘発 フレームを含むパケットが損失と検出された場合、QUIC は必要なフレームを 新しいパケット番号を持つ新しいパケットに含めます。これにより、ACK を受信したときに どのパケットが確認応答されたのかに関する曖昧さが取り除かれます。その結果、 より正確な RTT 測定が可能となり、偽の再送信は容易に検出され、Fast Retransmit などの メカニズムを、パケット番号のみに基づいて普遍的に適用できます。¶
この設計上の要点は、QUIC の損失検出メカニズムを大幅に簡素化します。 ほとんどの TCP メカニズムは、TCP シーケンス番号に基づいて 送信順序を暗黙に推定しようとします。これは、特に TCP タイムスタンプが 利用できない場合には、容易ではない作業です。¶
QUIC は、パケットが失われると損失エポックを開始します。損失エポックは、 そのエポックの開始後に送信されたいずれかのパケットが確認応答されると終了します。 TCP はシーケンス番号空間のギャップが埋まるのを待つため、セグメントが 連続して複数回失われると、損失エポックが複数のラウンドトリップにわたって 終了しないことがあります。どちらもエポックごとに輻輳ウィンドウを 1 回だけ 減らすべきであるため、QUIC は損失を経験した各ラウンドトリップごとに 1 回 それを行う一方、TCP は複数のラウンドトリップ全体で 1 回しか行わない場合があります。¶
QUIC ACK フレームには、TCP の Selective Acknowledgments(SACKs)[RFC2018] と同様の情報が含まれます。しかし、 QUIC はパケット 確認応答の reneging を許可しないため、両側の実装が大幅に簡素化され、 送信者のメモリ負荷が軽減されます。¶
QUIC は、TCP の 3 つの SACK 範囲とは対照的に、多数の ACK 範囲をサポートします。 損失の多い環境では、これにより回復が速くなり、偽の再送信が減少し、 タイムアウトに依存することなく前進が保証されます。¶
QUIC エンドポイントは、パケットが受信された時点から 対応する確認応答が送信される時点までに発生した遅延を測定します。 これにより、ピアはより正確な RTT 推定値を維持できます。Section 13.2 of [QUIC-TRANSPORT] を参照してください。¶
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 は、タイマーが満了したときに、プローブパケットが一時的に 輻輳ウィンドウを超えることを許可します。¶
TCP は 1 パケットの最小輻輳ウィンドウを使用します。しかし、その単一 パケットが失われると、送信者は回復のために PTO を待つ必要があります(Section 6.2)。 これは RTT よりはるかに長くなる可能性があります。単一の ACK 誘発パケットを送信することは、 受信者が確認応答を遅延させる場合に追加の遅延が発生する可能性も 高めます。¶
そのため QUIC は、最小輻輳ウィンドウを 2 パケットにすることを推奨します。これはネットワーク負荷を増加させますが、 持続的輻輳の下では送信者が送信レートを指数的に低下させ続けるため 安全とみなされます(Section 6.2)。¶
TCP は SYN または SYN-ACK パケットの損失を持続的輻輳として扱い、 輻輳ウィンドウを 1 パケットに減らします。[RFC5681] を参照してください。QUIC は ハンドシェイクデータを含むパケットの損失を、他の損失と同じように扱います。¶
高いレベルでは、エンドポイントは、パケットが送信されてから 確認応答されるまでの時間を RTT サンプルとして測定します。エンドポイントは RTT サンプルと ピアが報告したホスト遅延(Section 13.2 of [QUIC-TRANSPORT] を参照)を使用して、 ネットワークパスの RTT の統計的記述を生成します。エンドポイントは各パスについて 次の 3 つの値を計算します。一定期間にわたる最小値 (min_rtt)、指数加重移動平均(smoothed_rtt)、および 観測された RTT サンプルの平均偏差(この文書の残りでは「variation」と呼ぶ) (rttvar)です。¶
エンドポイントは、次の 2 つの条件を満たす ACK フレームを受信したときに RTT サンプルを生成します:¶
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 推定値が十分な履歴を保持することを保証する方法は、未解決の研究課題です。¶
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)。¶
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 サンプルを調整する場合、エンドポイントは:¶
さらに、対応する復号鍵がすぐに利用できない場合、エンドポイントは 確認応答の処理を延期することがあります。たとえば、クライアントは、 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¶
QUIC 送信者は、確認応答を使用して失われたパケットを検出し、PTO を使用して 確認応答が受信されることを保証します。Section 6.2 を参照してください。この節では、 これらのアルゴリズムについて説明します。¶
パケットが失われた場合、QUIC トランスポートはその損失から回復する必要があります。 たとえば、データを再送信する、更新されたフレームを送信する、または フレームを破棄します。詳細については、Section 13.3 of [QUIC-TRANSPORT] を参照してください。¶
損失検出は、RTT 測定および 輻輳制御とは異なり、パケット番号空間ごとに分離されています。これは、RTT と輻輳制御が パスの特性である一方、損失検出は鍵の可用性にも依存するためです。¶
確認応答に基づく損失検出は、TCP の Fast Retransmit [RFC5681]、Early Retransmit [RFC5827]、Forward Acknowledgment [FACK]、SACK loss recovery [RFC6675]、および RACK-TLP [RFC8985] の精神を実装します。この 節では、これらのアルゴリズムが QUIC でどのように実装されるかの概要を示します。¶
パケットは、次のすべての条件を満たす場合に損失と宣言されます:¶
確認応答は、後で送信されたパケットが配送されたことを示し、パケットしきい値と 時間しきい値は、パケットの並べ替えに対するある程度の許容を提供します。¶
パケットを誤って損失と宣言すると、不要な再送信につながり、 損失検出時の輻輳制御器の動作によって性能が低下する可能性があります。 実装は、偽の再送信を検出し、将来の偽の再送信と損失イベントを 減らすために、パケットまたは時間の並べ替えしきい値を増やすことができます。 適応的な時間しきい値を持つ実装は、回復遅延を最小化するために、 より小さい初期並べ替え しきい値から開始することを選択しても MAY。¶
パケット並べ替えしきい値 (kPacketThreshold)の初期値として RECOMMENDED される値は 3 であり、これは TCP 損失検出のベストプラクティス [RFC5681] [RFC6675] に基づきます。TCP と同様であり続けるため、 実装は 3 未満のパケットしきい値を使用するべきではありません (SHOULD NOT)。[RFC5681] を参照してください。¶
一部のネットワークではより高い程度のパケット並べ替えが発生し、 送信者が偽の損失を検出することがあります。さらに、QUIC では TCP よりも パケット並べ替えが一般的である可能性があります。これは、TCP パケットを観測し並べ替えることができるネットワーク要素が QUIC ではそれを行えず、 また QUIC パケット番号が暗号化されているためです。RACK [RFC8985] のように、損失を誤検出した後に並べ替えしきい値を増やす アルゴリズムは TCP で有用であることが証明されており、 QUIC でも少なくとも同じ程度に有用であると期待されます。¶
同じパケット番号空間内の後続パケットが確認応答された後、 エンドポイントは、以前のパケットがしきい値 量の時間だけ過去に送信されていた場合、そのパケットを損失と宣言する SHOULD。パケットを早すぎる時点で損失と宣言することを避けるため、 この時間しきい値は、kGranularity 定数で示されるローカルタイマー 粒度以上に設定され MUST。時間しきい値は次のとおりです:¶
max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)¶
最大の確認応答済みパケットより前に送信されたパケットをまだ 損失と宣言できない場合、残り時間に対してタイマーを設定する SHOULD。¶
max(smoothed_rtt, latest_rtt) を使用することで、次の 2 つの 場合から保護されます:¶
RTT 乗数として表される 時間しきい値(kTimeThreshold)の RECOMMENDED 値は 9/8 です。タイマー粒度 (kGranularity)の RECOMMENDED 値は 1 ミリ秒です。¶
実装は、絶対しきい値、以前のコネクションからのしきい値、 適応的しきい値、または RTT 変動を含めることを試しても MAY。 小さいしきい値は並べ替えに対する耐性を低下させ、偽の 再送信を増やします。一方、大きいしきい値は損失検出遅延を増加させます。¶
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] に基づきます。¶
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 NOT。 Section 6.1.2 を参照してください。時間 しきい値損失検出のために設定されたタイマーは、ほとんどの場合 PTO タイマーより早く満了し、データを偽って再送信する可能性が低くなります。¶
同じネットワーク上の再開されたコネクションは、以前のコネクションの 最終的な 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。これは、鍵の破棄が 前進を示し、損失検出タイマーが すでに破棄されたパケット番号空間に対して設定されていた可能性があるためです。¶
サーバーがそのパス上でクライアントのアドレスを検証するまで、 送信できるデータ量は、受信したデータ量の 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。¶
サーバーが重複した 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 つ以上のパケットと結合できます。¶
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 タイマーが複数回満了し、新しいデータを送信できない場合、 実装は毎回同じペイロードを送信するか、 異なるペイロードを送信するかを選択しなければなりません。 同じペイロードを送信する方が単純な場合があり、 最も優先度の高いフレームが先に到着することを保証します。毎回異なる ペイロードを送信すると、偽の再送信の可能性が減少します。¶
Retry パケットは、クライアントに別の Initial パケットを送信させ、 実質的にコネクション処理を再開します。Retry パケットは、Initial パケットが受信されたが処理されなかったことを示します。Retry パケットは、 パケットが処理されたことを示さず、パケット番号も 指定しないため、確認応答として扱うことはできません。¶
Retry パケットを受信したクライアントは、保留中のタイマーのリセットを含め、 輻輳制御および損失回復 状態をリセットします。その他のコネクション状態、特に 暗号ハンドシェイクメッセージは保持されます。 Section 17.2.5 of [QUIC-TRANSPORT] を参照してください。¶
クライアントは、最初の Initial パケットが送信された時点から Retry または Version Negotiation パケットが受信された時点までの期間として、サーバーへの RTT 推定値を計算しても MAY。 クライアントは、この値を初期 RTT 推定値のデフォルトの代わりに使用しても MAY。¶
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] を参照してください。¶
この文書は、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 を参照)に送信される場合を除きます。¶
パスが Explicit Congestion Notification(ECN) [RFC3168] [RFC8311] をサポートすることが検証されている場合、QUIC は IP ヘッダー内の Congestion Experienced(CE)コードポイントを輻輳のシグナルとして扱います。 この文書は、ピアが報告する ECN-CE カウントが増加したときの エンドポイントの応答を規定します。Section 13.4.2 of [QUIC-TRANSPORT] を参照してください。¶
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 です。¶
この文書で説明する 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
これらの状態およびそれらの間の遷移は、後続の 節で説明します。¶
NewReno 送信者は、輻輳ウィンドウが スロースタートしきい値を下回っている間は常にスロースタート状態にあります。 送信者は、スロースタートしきい値が無限大の値に初期化されるため、 スロースタートで開始します。¶
送信者がスロースタート中である間、各確認応答が処理されるたびに、 確認応答されたバイト数だけ輻輳ウィンドウが増加します。 これにより、輻輳ウィンドウは指数的に増加します。¶
送信者は、パケットが失われたとき、またはピアが報告する ECN-CE カウントが増加したときに、スロースタートを終了し、 回復期間に入らなければなりません(MUST)。¶
送信者は、輻輳ウィンドウがスロースタートしきい値より小さい場合には 常にスロースタートに再び入ります。これは持続的輻輳が 宣言された後にのみ発生します。¶
NewReno 送信者は、パケットの損失を検出したとき、または ピアが報告する ECN-CE カウントが増加したときに回復期間に入ります。 すでに回復期間中の送信者はその状態にとどまり、 再び入り直すことはありません。¶
回復期間に入るとき、送信者は、損失が検出されたときの 輻輳ウィンドウ値の半分にスロースタートしきい値を 設定しなければなりません(MUST)。輻輳 ウィンドウは、回復期間を終了する前に、スロースタートしきい値の 減少後の値に設定されなければなりません(MUST)。¶
実装は、回復期間に入った直後に輻輳 ウィンドウを減少させても MAY、または Proportional Rate Reduction [PRR] などの他のメカニズムを使用して、輻輳ウィンドウを より段階的に減少させてもかまいません。輻輳ウィンドウが直ちに減少される場合、 減少の前に 1 つのパケットを送信できます。これは、失われたパケット内のデータが 再送信される場合に損失回復を高速化し、Section 5 of [RFC6675] で説明される TCP と同様です。¶
回復期間は、輻輳ウィンドウの減少をラウンド トリップごとに 1 回に制限することを目的としています。したがって、回復期間中、 輻輳ウィンドウは、新たな損失や ECN-CE カウントの増加に応じて 変化しません。¶
回復期間中に送信されたパケットが確認応答されると、回復期間は終了し、 送信者は輻輳回避に入ります。これは、 回復を開始した失われたセグメントが確認応答されたときに回復が終了するという TCP の回復の定義とは少し異なります [RFC5681]。¶
NewReno 送信者は、輻輳ウィンドウが スロースタートしきい値以上であり、かつ回復期間中でない場合には 常に輻輳回避状態にあります。¶
輻輳回避中の送信者は、Additive Increase Multiplicative Decrease(AIMD)方式を使用します。この方式では、 1 つの輻輳ウィンドウ分が確認応答されるごとに、輻輳ウィンドウの増加を 最大で 1 つの最大データグラムサイズに制限しなければなりません (MUST)。¶
送信者は、パケットが失われたとき、またはピアが報告する ECN-CE カウントが増加したときに、輻輳回避を終了して 回復期間に入ります。¶
ハンドシェイク中、パケットが到着したときに一部のパケット保護鍵が 利用できないことがあり、受信者はそのパケットを破棄することを選択できます。 特に、Handshake および 0-RTT パケットは Initial パケットが 到着するまで処理できず、1-RTT パケットはハンドシェイクが完了するまで 処理できません。エンドポイントは、ピアがそれらのパケットを処理するための パケット保護鍵を持つ前に到着した可能性がある Handshake、0-RTT、および 1-RTT パケットの 損失を無視しても MAY。 エンドポイントは、あるパケット番号空間で最も早く確認応答されたパケットより後に 送信されたパケットの損失を無視しては MUST NOT。¶
プローブパケットは輻輳制御器によってブロックされては MUST NOT。ただし、送信者は これらのパケットを追加で転送中であるものとして算入しなければなりません (MUST)。これは、これらのパケットがパケット損失を確立することなく ネットワーク負荷を追加するためです。プローブ パケットの送信により、パケットの損失または配送を確立する確認応答を受信するまで、 送信者の bytes in flight が輻輳ウィンドウを超える可能性があることに注意してください。¶
送信者が、十分に長い期間にわたって送信されたすべてのパケットの 損失を確立した場合、ネットワークは持続的輻輳を経験していると みなされます。¶
持続的輻輳の継続時間は次のように計算されます:¶
(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 満了に依存せずに 持続的輻輳を確立できます。¶
送信者は、確認応答の受信後、ACK 誘発である 2 つのパケットが 損失と宣言され、かつ次の条件を満たす場合に、持続的輻輳を確立します:¶
これら 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)。¶
次の例は、送信者が持続的 輻輳をどのように確立し得るかを示します。次を仮定します:¶
smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay = 2 kPersistentCongestionThreshold = 3¶
次の一連のイベントを考えます:¶
| 時刻 | 動作 |
|---|---|
| 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 満了は必須ではありません。¶
送信者は、輻輳制御器からの入力に基づいて、すべての転送中 パケットの送信をペーシングする SHOULD。¶
複数のパケットを間に遅延なくネットワークへ送信すると、 短期的な輻輳や損失を引き起こす可能性のあるパケットバーストが発生します。 送信者は、ペーシングを使用するか、そのようなバーストを制限しなければなりません (MUST)。 送信者はバーストを初期輻輳ウィンドウに制限する SHOULD。Section 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 つとして、リーキーバケット アルゴリズムを使用する方法があります。この場合、「バケット」の容量は最大バーストサイズに制限され、 「バケット」が満たされるレートは上記の関数によって決定されます。¶
bytes in flight が輻輳ウィンドウより小さく、送信が ペーシングによって制限されていない場合、輻輳ウィンドウは利用不足です。 これは、アプリケーションデータの不足やフロー制御制限によって発生することがあります。 これが発生した場合、輻輳ウィンドウはスロースタートまたは 輻輳回避のいずれにおいても増加されるべきではありません (SHOULD NOT)。¶
パケットをペーシングする送信者(Section 7.7 を参照)は、 この遅延によりパケット送信を遅らせ、輻輳ウィンドウを十分に 利用しないことがあります。送信者は、ペーシング遅延がなければ輻輳ウィンドウを十分に 利用していたはずである場合、自身をアプリケーション制限状態とみなすべきではありません (SHOULD NOT)。¶
送信者は、利用不足の期間の後に輻輳ウィンドウを更新するため、 [RFC7661] で TCP 向けに提案されているものなど、 代替メカニズムを実装しても MAY。¶
損失検出および輻輳制御は、基本的に、認証されていない エンティティからの遅延、損失、ECN マーキングなどのシグナルを 消費することを伴います。攻撃者は、パケットをドロップする、パス遅延を 戦略的に変更する、または ECN コードポイントを変更することで、 これらのシグナルを操作し、エンドポイントの送信レートを低下させることができます。¶
ACK フレームのみを運ぶパケットは、パケットサイズを観測することで ヒューリスティックに識別できます。確認応答パターンは、リンク 特性またはアプリケーションの挙動に関する情報を露出させる可能性があります。 漏えいする情報を減らすため、エンドポイントは確認応答を他のフレームと 束ねることができます。または、性能への潜在的なコストを伴って PADDING フレームを使用できます。¶
受信者は、送信者の輻輳応答を変更するために ECN マーキングを誤って報告できます。ECN-CE マーキングの報告を抑制すると、 送信者の送信レートが増加する可能性があります。この増加は 輻輳および損失を引き起こす可能性があります。¶
送信者は、送信する時折のパケットに ECN-CE マーキングを付けることで、 報告の抑制を検出できます。ECN-CE マーキング付きで送信されたパケットが 確認応答されたときに CE マーク付きであったと報告されない場合、 送信者は、そのパス上で後続に送信されるパケットに ECN-Capable Transport(ECT) コードポイントを設定しないことで、そのパスの ECN を無効にできます [RFC3168]。¶
追加の ECN-CE マーキングを報告すると、送信者は 送信レートを低下させます。これは、接続のフロー制御 制限を低く通知することと効果が似ているため、そうすることによって 利点は得られません。¶
エンドポイントは、使用する輻輳制御器を選択します。輻輳 制御器は ECN-CE の報告に対してレートを下げることで応答しますが、 その応答は異なる場合があります。マーキングは損失と同等に扱うことができます [RFC3168] が、[RFC8511] または [RFC8311] のように、 他の応答を規定することもできます。¶
ここでは、第 6 節で説明した損失検出メカニズムの 実装例について説明します。¶
この節の擬似コード片はコードコンポーネントとしてライセンスされています。著作権 表示を参照してください。¶
輻輳制御を正しく実装するため、QUIC 送信者はすべての ACK 誘発パケットを、そのパケットが確認応答されるか損失するまで追跡します。 実装は、パケット番号および暗号コンテキストによってこの情報にアクセスし、 損失回復および輻輳制御のためにパケットごとのフィールド (付録 A.1.1)を保存できることが期待されます。¶
パケットが損失と宣言された後でも、エンドポイントはパケットの並べ替えを 許容するため、一定時間その状態を維持できます。第 13.3 節 of [QUIC-TRANSPORT]を参照してください。 これにより、送信者は偽の再送信を検出できます。¶
送信済みパケットはパケット番号空間ごとに追跡され、ACK 処理は単一の空間にのみ適用されます。¶
送信済みパケットのパケット番号。¶
パケットが ACK 誘発であるかどうかを示す Boolean。 true の場合、ピアがその ACK を含む ACK フレームの送信を max_ack_delay まで遅らせる可能性はありますが、確認応答が受信されることが 期待されます。¶
そのパケットが bytes in flight に算入されるかどうかを示す Boolean。¶
パケット内で送信されたバイト数。UDP または IP オーバーヘッドは含まず、QUIC フレーミングオーバーヘッドは含みます。¶
パケットが送信された時刻。¶
損失回復で使用される定数は、RFC、論文、および 一般的な実践の組み合わせに基づいています。¶
パケットしきい値による損失検出がパケットを損失とみなす前の、 パケット単位での最大並べ替え量。第 6.1.1 節で推奨される値は 3 です。¶
時間しきい値による損失検出がパケットを損失とみなす前の、 時間単位での最大並べ替え量。RTT 乗数として指定されます。第 6.1.2 節で推奨される値は 9/8 です。¶
3 つのパケット番号空間を列挙する enum:¶
enum kPacketNumberSpace {
Initial,
Handshake,
ApplicationData,
}
¶
輻輳制御メカニズムを実装するために必要な変数について、 この節で説明します。¶
以前に未確認応答だったパケットに対する確認応答を受信したときに行われた、 最新の RTT 測定値。¶
最初の RTT サンプルが取得された時刻。¶
受信者が Application Data パケット番号空間のパケットに対する 確認応答を遅延させる意図のある最大時間。これは同名のトランスポートパラメーター (第 18.2 節 of [QUIC-TRANSPORT])によって定義されます。受信した ACK フレーム内の実際の ack_delay は、遅延したタイマー、並べ替え、または損失により さらに大きくなる可能性があることに注意してください。¶
損失検出に使用されるマルチモーダルタイマー。¶
確認応答を受信せずに PTO が送信された回数。¶
最新の ACK 誘発パケットが送信された時刻。¶
そのパケット番号空間でこれまでに確認応答された最大のパケット番号。¶
そのパケット番号空間の次のパケットが、時間上の並べ替えウィンドウを 超過したことに基づいて損失とみなされ得る時刻。¶
コネクションの開始時に、損失検出変数を次のように初期化します:¶
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¶
パケットが送信された後、そのパケットに関する情報が保存されます。 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()
¶
サーバーが増幅防止制限によってブロックされている場合、 データグラムを受信すると、そのデータグラム内のパケットが 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()
¶
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
¶
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)
¶
損失検出タイマーが満了すると、タイマーのモードによって 実行される動作が決まります。¶
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()
¶
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
¶
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()¶
ここでは、第 7 節で説明した 輻輳制御器の実装例について説明します。¶
この節の擬似コード片はコードコンポーネントとしてライセンスされています。著作権 表示を参照してください。¶
輻輳制御メカニズムを実装するために必要な変数について、 この節で説明します。¶
送信者の現在の最大ペイロードサイズ。UDP または IP オーバーヘッドは含みません。最大データグラムサイズは、輻輳ウィンドウ 計算に使用されます。エンドポイントは、Path Maximum Transmission Unit (PMTU。第 14.2 節 of [QUIC-TRANSPORT]を参照)に基づいて この変数の値を設定し、最小値は 1200 バイトです。¶
ACK フレーム内でピアによってパケット番号空間について報告された ECN-CE カウンターの最高値。この値は、報告された ECN-CE カウンターの 増加を検出するために使用されます。¶
少なくとも 1 つの ACK 誘発フレームまたは PADDING フレームを含み、 まだ確認応答されておらず、損失とも宣言されていない、すべての送信済みパケットの バイト単位のサイズの合計。このサイズには IP または UDP オーバーヘッドは含まれませんが、 QUIC ヘッダーおよび Authenticated Encryption with Associated Data (AEAD)オーバーヘッドは含まれます。ACK フレームのみを含むパケットは、 輻輳制御が輻輳フィードバックを妨げないように、bytes_in_flight に 算入されません。¶
転送中であることが許可される最大バイト数。¶
損失または ECN の検出により現在の回復期間が開始した時刻。 この時刻の後に送信されたパケットが確認応答されると、QUIC は 輻輳回復を終了します。¶
バイト単位のスロースタートしきい値。輻輳ウィンドウが ssthresh を下回る場合、モードはスロースタートであり、ウィンドウは 確認応答されたバイト数だけ増加します。¶
輻輳制御擬似コードは、損失回復擬似コードの一部の変数にも アクセスします。¶
コネクションの開始時に、輻輳制御変数を 次のように初期化します:¶
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¶
パケットが送信され、それが非 ACK フレームを含むたびに、そのパケットは bytes_in_flight を増加させます。¶
OnPacketSentCC(sent_bytes): bytes_in_flight += sent_bytes¶
これは、損失検出の 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
¶
これは、新しい輻輳イベントが検出されたときに、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()
¶
これは、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)
¶
これは、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
¶
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
¶