Aller au contenu principal

7. Contrôle de congestion (Congestion Control)

Ce document spécifie un contrôleur de congestion côté expéditeur pour QUIC similaire à TCP NewReno [RFC6582].

Les signaux que QUIC fournit pour le contrôle de congestion sont génériques et sont conçus pour prendre en charge différents algorithmes côté expéditeur. Un expéditeur peut choisir unilatéralement d'utiliser un algorithme différent, tel que CUBIC [RFC8312].

Si un expéditeur utilise un contrôleur différent de celui spécifié dans ce document, le contrôleur choisi DOIT (MUST) se conformer aux directives de contrôle de congestion spécifiées dans la Section 3.1 de [RFC8085].

Similairement à TCP, les paquets contenant uniquement des trames ACK ne comptent pas dans les octets en vol (bytes in flight) et ne sont pas contrôlés par la congestion. Contrairement à TCP, QUIC peut détecter la perte de ces paquets et PEUT (MAY) utiliser cette information pour ajuster le contrôleur de congestion ou le débit des paquets ACK uniquement, mais ce document ne décrit pas de mécanisme pour ce faire.

Le contrôleur de congestion est par chemin (per path), donc les paquets envoyés sur d'autres chemins ne modifient pas le contrôleur de congestion du chemin actuel, comme décrit dans la Section 9.4 de [QUIC-TRANSPORT].

L'algorithme dans ce document spécifie et utilise la fenêtre de congestion du contrôleur en octets.

Un terminal NE DOIT PAS (MUST NOT) envoyer un paquet s'il entraînerait bytes_in_flight (voir Annexe B.2) à être plus grand que la fenêtre de congestion, sauf si le paquet est envoyé lors de l'expiration d'un temporisateur PTO (voir Section 6.2) ou lors de l'entrée en récupération (voir Section 7.3.2).


7.1. Notification explicite de congestion (Explicit Congestion Notification)

Si un chemin a été validé pour prendre en charge la notification explicite de congestion (ECN) [RFC3168] [RFC8311], QUIC traite un point de code de congestion expérimentée (Congestion Experienced, CE) dans l'en-tête IP comme un signal de congestion. Ce document spécifie la réponse d'un terminal lorsque le compteur ECN-CE rapporté par le pair augmente ; voir la Section 13.4.2 de [QUIC-TRANSPORT].


7.2. Fenêtre de congestion initiale et minimale (Initial and Minimum Congestion Window)

QUIC commence chaque connexion en démarrage lent (slow start) avec la fenêtre de congestion définie à une valeur initiale. Les terminaux DEVRAIENT (SHOULD) utiliser une fenêtre de congestion initiale de dix fois la taille maximale du datagramme (max_datagram_size), tout en limitant la fenêtre à la plus grande valeur entre 14 720 octets ou deux fois la taille maximale du datagramme. Cela suit l'analyse et les recommandations dans [RFC6928], en augmentant la limite d'octets pour tenir compte de la surcharge de 8 octets plus petite d'UDP par rapport à la surcharge de 20 octets pour TCP.

Si la taille maximale du datagramme change pendant la connexion, la fenêtre de congestion initiale DEVRAIT (SHOULD) être recalculée avec la nouvelle taille. Si la taille maximale du datagramme est diminuée afin de compléter la poignée de main, la fenêtre de congestion DEVRAIT (SHOULD) être définie à la nouvelle fenêtre de congestion initiale.

Avant de valider l'adresse du client, le serveur peut être davantage limité par la limite anti-amplification comme spécifié dans la Section 8.1 de [QUIC-TRANSPORT]. Bien que la limite anti-amplification puisse empêcher la fenêtre de congestion d'être pleinement utilisée et donc ralentir l'augmentation de la fenêtre de congestion, elle n'affecte pas directement la fenêtre de congestion.

La fenêtre de congestion minimale est la plus petite valeur que la fenêtre de congestion peut atteindre en réponse à une perte, une augmentation du compteur ECN-CE rapporté par le pair, ou une congestion persistante. La valeur RECOMMANDÉE (RECOMMENDED) est 2 * max_datagram_size.


7.3. États du contrôle de congestion (Congestion Control States)

Le contrôleur de congestion NewReno décrit dans ce document a trois états distincts, comme illustré dans la Figure 1.

       Nouveau chemin ou         +------------+
congestion persistante | Démarrage |
(O)----------------------> | Lent |
+------------+
|
Perte ou
augmentation ECN-CE
|
v
+------------+ Perte ou +------------+
| Évitement | augmentation | Période de |
| Congestion | ECN-CE | Récupération|
+------------+---------------->+------------+
^ |
| |
+----------------------------+
Accusé de réception de paquet
envoyé pendant la récupération

Figure 1 : États et transitions du contrôle de congestion

Ces états et les transitions entre eux sont décrits dans les sections suivantes.

7.3.1. Démarrage lent (Slow Start)

Un expéditeur NewReno est en démarrage lent chaque fois que la fenêtre de congestion est inférieure au seuil de démarrage lent. Un expéditeur commence en démarrage lent car le seuil de démarrage lent est initialisé à une valeur infinie.

Pendant qu'un expéditeur est en démarrage lent, la fenêtre de congestion augmente du nombre d'octets accusés réception lorsque chaque accusé de réception est traité. Cela entraîne une croissance exponentielle de la fenêtre de congestion.

L'expéditeur DOIT (MUST) quitter le démarrage lent et entrer dans une période de récupération lorsqu'un paquet est perdu ou lorsque le compteur ECN-CE rapporté par son pair augmente.

Un expéditeur rentre en démarrage lent chaque fois que la fenêtre de congestion est inférieure au seuil de démarrage lent, ce qui ne se produit qu'après la déclaration d'une congestion persistante.

7.3.2. Récupération (Recovery)

Un expéditeur NewReno entre dans une période de récupération lorsqu'il détecte la perte d'un paquet ou lorsque le compteur ECN-CE rapporté par son pair augmente. Un expéditeur qui est déjà dans une période de récupération y reste et n'y rentre pas à nouveau.

En entrant dans une période de récupération, un expéditeur DOIT (MUST) définir le seuil de démarrage lent à la moitié de la valeur de la fenêtre de congestion lorsque la perte est détectée. La fenêtre de congestion DOIT (MUST) être définie à la valeur réduite du seuil de démarrage lent avant de quitter la période de récupération.

Les implémentations PEUVENT (MAY) réduire la fenêtre de congestion immédiatement en entrant dans une période de récupération ou utiliser d'autres mécanismes, tels que la réduction de débit proportionnelle (Proportional Rate Reduction) [PRR], pour réduire la fenêtre de congestion plus progressivement. Si la fenêtre de congestion est réduite immédiatement, un seul paquet peut être envoyé avant la réduction. Cela accélère la récupération de perte si les données dans le paquet perdu sont retransmises et est similaire à TCP comme décrit dans la Section 5 de [RFC6675].

La période de récupération vise à limiter la réduction de la fenêtre de congestion à une fois par aller-retour. Par conséquent, pendant une période de récupération, la fenêtre de congestion ne change pas en réponse à de nouvelles pertes ou augmentations du compteur ECN-CE.

Une période de récupération se termine et l'expéditeur entre dans l'évitement de congestion lorsqu'un paquet envoyé pendant la période de récupération est accusé réception. Ceci est légèrement différent de la définition de récupération de TCP, qui se termine lorsque le segment perdu qui a démarré la récupération est accusé réception [RFC5681].

7.3.3. Évitement de congestion (Congestion Avoidance)

Un expéditeur NewReno est en évitement de congestion chaque fois que la fenêtre de congestion est égale ou supérieure au seuil de démarrage lent et n'est pas dans une période de récupération.

Un expéditeur en évitement de congestion utilise une approche d'augmentation additive diminution multiplicative (Additive Increase Multiplicative Decrease, AIMD) qui DOIT (MUST) limiter l'augmentation de la fenêtre de congestion à au plus une taille de datagramme maximale pour chaque fenêtre de congestion qui est accusée réception.

L'expéditeur quitte l'évitement de congestion et entre dans une période de récupération lorsqu'un paquet est perdu ou lorsque le compteur ECN-CE rapporté par son pair augmente.


7.4. Ignorer la perte de paquets indéchiffrables (Ignoring Loss of Undecryptable Packets)

Pendant la poignée de main, certaines clés de protection de paquet peuvent ne pas être disponibles lorsqu'un paquet arrive, et le récepteur peut choisir de supprimer le paquet. En particulier, les paquets Handshake et 0-RTT ne peuvent pas être traités jusqu'à ce que les paquets Initial arrivent, et les paquets 1-RTT ne peuvent pas être traités jusqu'à ce que la poignée de main soit terminée. Les terminaux PEUVENT (MAY) ignorer la perte de paquets Handshake, 0-RTT et 1-RTT qui pourraient être arrivés avant que le pair ait les clés de protection de paquet pour traiter ces paquets. Les terminaux NE DOIVENT PAS (MUST NOT) ignorer la perte de paquets qui ont été envoyés après le paquet accusé réception le plus tôt dans un espace de numéro de paquet donné.


7.5. Temporisation de sonde (Probe Timeout)

Les paquets de sonde NE DOIVENT PAS (MUST NOT) être bloqués par le contrôleur de congestion. Cependant, un expéditeur DOIT (MUST) compter ces paquets comme étant en plus en vol, car ces paquets ajoutent une charge réseau sans établir de perte de paquet. Notez que l'envoi de paquets de sonde pourrait faire en sorte que les octets en vol de l'expéditeur dépassent la fenêtre de congestion jusqu'à ce qu'un accusé de réception soit reçu qui établit la perte ou la livraison de paquets.


7.6. Congestion persistante (Persistent Congestion)

Lorsqu'un expéditeur établit la perte de tous les paquets envoyés sur une durée suffisamment longue, le réseau est considéré comme subissant une congestion persistante.

7.6.1. Durée (Duration)

La durée de congestion persistante est calculée comme suit :

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

Contrairement au calcul PTO dans la Section 6.2, cette durée inclut le max_ack_delay indépendamment des espaces de numéro de paquet dans lesquels les pertes sont établies.

Cette durée permet à un expéditeur d'envoyer autant de paquets avant d'établir une congestion persistante, y compris certains en réponse à l'expiration PTO, que TCP le fait avec les sondes de perte de queue (Tail Loss Probes) [RFC8985] et un RTO [RFC5681].

Des valeurs plus grandes de kPersistentCongestionThreshold font que l'expéditeur devient moins réactif à la congestion persistante dans le réseau, ce qui peut entraîner un envoi agressif dans un réseau congestionné. Une valeur trop petite peut entraîner un expéditeur déclarant une congestion persistante inutilement, entraînant un débit réduit pour l'expéditeur.

La valeur RECOMMANDÉE (RECOMMENDED) pour kPersistentCongestionThreshold est 3, ce qui entraîne un comportement approximativement équivalent à un expéditeur TCP déclarant un RTO après deux TLP.

Cette conception n'utilise pas d'événements PTO consécutifs pour établir une congestion persistante, car les modèles d'application impactent l'expiration PTO. Par exemple, un expéditeur qui envoie de petites quantités de données avec des périodes de silence entre elles redémarre le temporisateur PTO chaque fois qu'il envoie, empêchant potentiellement le temporisateur PTO d'expirer pendant une longue période, même lorsqu'aucun accusé de réception n'est reçu. L'utilisation d'une durée permet à un expéditeur d'établir une congestion persistante sans dépendre de l'expiration PTO.

7.6.2. Établissement de la congestion persistante (Establishing Persistent Congestion)

Un expéditeur établit une congestion persistante après la réception d'un accusé de réception si deux paquets qui sollicitent un accusé de réception sont déclarés perdus, et :

  • dans tous les espaces de numéro de paquet, aucun des paquets envoyés entre les temps d'envoi de ces deux paquets n'est accusé réception ;

  • la durée entre les temps d'envoi de ces deux paquets dépasse la durée de congestion persistante (Section 7.6.1) ; et

  • un échantillon RTT antérieur existait lorsque ces deux paquets ont été envoyés.

Ces deux paquets DOIVENT (MUST) solliciter un accusé de réception, car un récepteur est tenu d'accuser réception uniquement des paquets sollicitant un accusé de réception dans son délai d'accusé de réception maximum ; voir la Section 13.2 de [QUIC-TRANSPORT].

La période de congestion persistante NE DEVRAIT PAS (SHOULD NOT) commencer jusqu'à ce qu'il y ait au moins un échantillon RTT. Avant le premier échantillon RTT, un expéditeur arme son temporisateur PTO en fonction du RTT initial (Section 6.2.2), qui pourrait être substantiellement plus grand que le RTT réel. Exiger un échantillon RTT antérieur empêche un expéditeur d'établir une congestion persistante avec potentiellement trop peu de sondes.

Puisque la congestion réseau n'est pas affectée par les espaces de numéro de paquet, la congestion persistante DEVRAIT (SHOULD) considérer les paquets envoyés à travers les espaces de numéro de paquet. Un expéditeur qui n'a pas d'état pour tous les espaces de numéro de paquet ou une implémentation qui ne peut pas comparer les temps d'envoi à travers les espaces de numéro de paquet PEUT (MAY) utiliser l'état uniquement pour l'espace de numéro de paquet qui a été accusé réception. Cela pourrait entraîner une déclaration erronée de congestion persistante, mais cela ne conduira pas à un échec de détection de la congestion persistante.

Lorsqu'une congestion persistante est déclarée, la fenêtre de congestion de l'expéditeur DOIT (MUST) être réduite à la fenêtre de congestion minimale (kMinimumWindow), similaire à la réponse d'un expéditeur TCP sur un RTO [RFC5681].

7.6.3. Exemple (Example)

L'exemple suivant illustre comment un expéditeur pourrait établir une congestion persistante. Supposons :

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

Considérez la séquence d'événements suivante :

TempsAction
t=0Envoyer le paquet #1 (données d'application)
t=1Envoyer le paquet #2 (données d'application)
t=1.2Recevoir l'accusé de réception de #1
t=2Envoyer le paquet #3 (données d'application)
t=3Envoyer le paquet #4 (données d'application)
t=4Envoyer le paquet #5 (données d'application)
t=5Envoyer le paquet #6 (données d'application)
t=6Envoyer le paquet #7 (données d'application)
t=8Envoyer le paquet #8 (PTO 1)
t=12Envoyer le paquet #9 (PTO 2)
t=12.2Recevoir l'accusé de réception de #9

Les paquets 2 à 8 sont déclarés perdus lorsque l'accusé de réception pour le paquet 9 est reçu à t = 12.2.

La période de congestion est calculée comme le temps entre les paquets perdus les plus anciens et les plus récents : 8 - 1 = 7. La durée de congestion persistante est 2 * 3 = 6. Parce que le seuil a été atteint et qu'aucun des paquets entre les paquets perdus les plus anciens et les plus récents n'a été accusé réception, le réseau est considéré comme ayant subi une congestion persistante.

Bien que cet exemple montre l'expiration PTO, ils ne sont pas requis pour qu'une congestion persistante soit établie.


7.7. Cadence (Pacing)

Un expéditeur DEVRAIT (SHOULD) cadencer l'envoi de tous les paquets en vol en fonction de l'entrée du contrôleur de congestion.

L'envoi de plusieurs paquets dans le réseau sans délai entre eux crée une rafale de paquets qui pourrait causer une congestion à court terme et des pertes. Les expéditeurs DOIVENT (MUST) soit utiliser le cadençage, soit limiter de telles rafales. Les expéditeurs DEVRAIENT (SHOULD) limiter les rafales à la fenêtre de congestion initiale ; voir la Section 7.2. Un expéditeur sachant que le chemin réseau vers le récepteur peut absorber des rafales plus grandes PEUT (MAY) utiliser une limite plus élevée.

Une implémentation devrait veiller à architecturer son contrôleur de congestion pour bien fonctionner avec un cadenceur. Par exemple, un cadenceur pourrait envelopper le contrôleur de congestion et contrôler la disponibilité de la fenêtre de congestion, ou un cadenceur pourrait cadencer les paquets qui lui sont remis par le contrôleur de congestion.

La livraison en temps opportun des trames ACK est importante pour une récupération de perte efficace. Pour éviter de retarder leur livraison au pair, les paquets contenant uniquement des trames ACK NE DEVRAIENT donc PAS (SHOULD NOT) être cadencés.

Les terminaux peuvent implémenter le cadençage comme ils le choisissent. Un expéditeur parfaitement cadencé répartit les paquets exactement uniformément dans le temps. Pour un contrôleur de congestion basé sur une fenêtre, tel que celui de ce document, ce débit peut être calculé en moyennant la fenêtre de congestion sur le RTT. Exprimé comme un débit en unités d'octets par temps, où congestion_window est en octets :

rate = N * congestion_window / smoothed_rtt

Ou exprimé comme un intervalle inter-paquet en unités de temps :

interval = (smoothed_rtt * packet_size / congestion_window) / N

L'utilisation d'une valeur pour N qui est petite, mais au moins 1 (par exemple, 1.25) garantit que les variations de RTT n'entraînent pas une sous-utilisation de la fenêtre de congestion.

Des considérations pratiques, telles que la paquetisation, les délais d'ordonnancement et l'efficacité computationnelle, peuvent amener un expéditeur à dévier de ce débit sur des périodes de temps beaucoup plus courtes qu'un RTT.

Une stratégie d'implémentation possible pour le cadençage utilise un algorithme de seau percé, où la capacité du "seau" est limitée à la taille de rafale maximale et le débit de remplissage du "seau" est déterminé par la fonction ci-dessus.


7.8. Sous-utilisation de la fenêtre de congestion (Underutilizing the Congestion Window)

Lorsque les octets en vol sont plus petits que la fenêtre de congestion et que l'envoi n'est pas limité par le cadençage, la fenêtre de congestion est sous-utilisée. Cela peut se produire en raison de données d'application insuffisantes ou de limites de contrôle de flux. Lorsque cela se produit, la fenêtre de congestion NE DEVRAIT PAS (SHOULD NOT) être augmentée en démarrage lent ou en évitement de congestion.

Un expéditeur qui cadence les paquets (voir la Section 7.7) pourrait retarder l'envoi de paquets et ne pas pleinement utiliser la fenêtre de congestion en raison de ce délai. Un expéditeur NE DEVRAIT PAS (SHOULD NOT) se considérer comme limité par l'application s'il aurait pleinement utilisé la fenêtre de congestion sans délai de cadençage.

Un expéditeur PEUT (MAY) implémenter des mécanismes alternatifs pour mettre à jour sa fenêtre de congestion après des périodes de sous-utilisation, tels que ceux proposés pour TCP dans [RFC7661].