5. Connexions (Connections)
5. Connexions
Une connexion QUIC est un état partagé entre un client et un serveur.
Chaque connexion commence par une phase de négociation, pendant laquelle les deux points de terminaison établissent des clés partagées et négocient le protocole d'application en utilisant le protocole de négociation cryptographique [QUIC-TLS]. La négociation (section 7) confirme que les deux points de terminaison sont disposés à communiquer (section 8.1) et établit les paramètres de la connexion (section 7.4).
Le protocole d'application peut utiliser la connexion pendant la phase de négociation, mais avec certaines limitations. 0-RTT permet au client d'envoyer des données d'application avant de recevoir une réponse du serveur. Cependant, 0-RTT ne fournit pas de protection contre les attaques par rejeu ; voir la section 9.2 de [QUIC-TLS]. Le serveur peut également envoyer des données d'application au client avant de recevoir les messages de négociation cryptographique finaux qui lui permettent de confirmer l'identité et l'activité du client. Ces capacités permettent aux protocoles d'application de fournir des options qui échangent certaines garanties de sécurité contre une réduction de la latence.
L'utilisation des ID de connexion (section 5.1) permet à une connexion de migrer vers un nouveau chemin réseau, à la fois comme choix direct d'un point de terminaison et lorsqu'elle est forcée par des changements de middlebox. La section 9 décrit les atténuations des problèmes de sécurité et de confidentialité liés à la migration.
Pour les connexions qui ne sont plus nécessaires ou désirées, le client et le serveur disposent de plusieurs moyens de mettre fin à la connexion, comme décrit dans la section 10.
5.1 ID de connexion
Chaque connexion possède un ensemble d'identifiants de connexion ou ID de connexion, chacun pouvant identifier la connexion. Les ID de connexion sont sélectionnés indépendamment par les points de terminaison ; chaque point de terminaison sélectionne l'ID de connexion que son homologue utilise.
La fonction principale d'un ID de connexion est de s'assurer que les changements d'adressage des couches de protocole inférieures (UDP, IP) ne provoquent pas la livraison de paquets de connexion QUIC au mauvais point de terminaison. Chaque point de terminaison sélectionne les ID de connexion en utilisant une méthode spécifique à l'implémentation (et possiblement spécifique au déploiement) qui permettra aux paquets portant cet ID de connexion d'être acheminés vers le point de terminaison et identifiés par le point de terminaison lors de la réception.
Plusieurs ID de connexion sont utilisés afin que les points de terminaison puissent envoyer des paquets qui ne peuvent pas être identifiés comme appartenant à la même connexion par un observateur sans la coopération du point de terminaison ; voir la section 9.5.
Les ID de connexion ne doivent contenir aucune information qu'un observateur externe (c'est-à-dire un observateur sans coopération avec l'émetteur) pourrait utiliser pour les corréler avec d'autres ID de connexion de la même connexion. Comme exemple simple, cela signifie que le même ID de connexion ne doit pas être émis plusieurs fois sur la même connexion.
Les paquets avec un en-tête long incluent les champs ID de connexion source (Source Connection ID) et ID de connexion de destination (Destination Connection ID). Ces champs sont utilisés pour établir les ID de connexion pour les nouvelles connexions ; voir la section 7.2 pour plus de détails.
Les paquets avec un en-tête court (section 17.3) incluent uniquement l'ID de connexion de destination et omettent la longueur explicite. La longueur du champ ID de connexion de destination devrait être connue du point de terminaison. Les points de terminaison utilisant un équilibreur de charge qui route basé sur l'ID de connexion peuvent s'entendre avec l'équilibreur de charge sur une longueur fixe pour les ID de connexion, ou sur un schéma d'encodage. Une partie fixe peut encoder une longueur explicite, ce qui permet à la longueur de l'ID de connexion entier de varier tout en étant utilisée par l'équilibreur de charge.
Les paquets de négociation de version (section 17.2.1) renvoient en écho l'ID de connexion sélectionné par le client, à la fois pour garantir le routage correct vers le client et pour démontrer que le paquet est une réponse à un paquet envoyé par le client.
Un ID de connexion de longueur nulle peut être utilisé lorsque l'ID de connexion n'est pas nécessaire pour router vers le point de terminaison correct. Cependant, lors de l'utilisation d'un ID de connexion de longueur nulle, le multiplexage des connexions sur la même adresse IP locale et le même port entraînera des échecs en présence de migration de connexion homologue, de rebinding NAT et de réutilisation de port client. Un point de terminaison ne doit pas utiliser la même adresse IP et le même port pour plusieurs connexions concurrentes avec un ID de connexion de longueur nulle, sauf s'il est certain que ces fonctionnalités de protocole ne sont pas utilisées.
Lorsqu'un point de terminaison utilise un ID de connexion de longueur non nulle, il doit s'assurer que l'homologue dispose de suffisamment d'ID de connexion parmi lesquels choisir pour les paquets envoyés au point de terminaison. Ces ID de connexion sont fournis par le point de terminaison en utilisant des trames NEW_CONNECTION_ID (section 19.15).
5.1.1 Émission des ID de connexion (Issuing Connection IDs)
Chaque ID de connexion a un numéro de séquence associé pour aider à détecter quand les trames NEW_CONNECTION_ID ou RETIRE_CONNECTION_ID font référence à la même valeur. L'ID de connexion initial émis par un point de terminaison est envoyé dans le champ ID de connexion source de l'en-tête de paquet long (section 17.2) pendant la négociation. Le numéro de séquence de l'ID de connexion initial est 0. Si le paramètre de transport preferred_address est envoyé, l'ID de connexion fourni a un numéro de séquence de 1.
Les ID de connexion supplémentaires sont communiqués à l'homologue en utilisant des trames NEW_CONNECTION_ID (section 19.15). Le numéro de séquence sur chaque ID de connexion nouvellement émis doit augmenter de 1. L'ID de connexion que le client sélectionne pour le premier champ ID de connexion de destination qu'il envoie et tout ID de connexion fourni par un paquet Retry ne se voient pas attribuer de numéros de séquence.
Lorsqu'un point de terminaison émet un ID de connexion, il doit accepter les paquets portant cet ID de connexion pendant la durée de la connexion ou jusqu'à ce que son homologue invalide l'ID de connexion via une trame RETIRE_CONNECTION_ID (section 19.16). Les ID de connexion émis et non retirés sont considérés comme actifs ; tout ID de connexion actif est valide pour la connexion actuelle à tout moment et dans tout type de paquet. Cela inclut l'ID de connexion émis par le serveur via le paramètre de transport preferred_address.
Un point de terminaison devrait s'assurer que son homologue dispose d'un nombre suffisant d'ID de connexion disponibles et non utilisés. Les points de terminaison annoncent le nombre d'ID de connexion actifs qu'ils sont prêts à maintenir en utilisant le paramètre de transport active_connection_id_limit. Un point de terminaison ne doit pas fournir plus d'ID de connexion que la limite de l'homologue. Un point de terminaison peut envoyer des ID de connexion qui dépassent temporairement la limite de l'homologue si la trame NEW_CONNECTION_ID exige également le retrait de toute partie excédentaire en incluant une valeur suffisamment grande dans le champ Retire Prior To.
Une trame NEW_CONNECTION_ID peut entraîner l'ajout de certains ID de connexion actifs et le retrait d'autres ID de connexion actifs par le point de terminaison en fonction de la valeur du champ Retire Prior To. Après le traitement d'une trame NEW_CONNECTION_ID et l'ajout et le retrait d'ID de connexion actifs, si le nombre d'ID de connexion actifs dépasse la valeur annoncée dans le paramètre de transport active_connection_id_limit, le point de terminaison doit fermer la connexion avec une erreur de type CONNECTION_ID_LIMIT_ERROR.
Lorsque l'homologue retire un ID de connexion, le point de terminaison devrait fournir un nouvel ID de connexion. Si le point de terminaison a fourni moins d'ID de connexion que l'active_connection_id_limit de l'homologue, il peut fournir un nouvel ID de connexion lorsqu'il reçoit un paquet avec un ID de connexion précédemment inutilisé. Un point de terminaison peut limiter le nombre total d'ID de connexion émis par connexion pour éviter le risque d'épuiser les ID de connexion ; voir la section 10.3.2. Un point de terminaison peut également limiter l'émission d'ID de connexion pour réduire la quantité d'état par chemin qu'il maintient, comme l'état de validation de chemin, car son homologue pourrait interagir avec lui via autant de chemins qu'il y a d'ID de connexion émis.
Un point de terminaison qui initie une migration et nécessite un ID de connexion de longueur non nulle devrait s'assurer que le pool d'ID de connexion disponibles pour l'homologue permet à l'homologue d'utiliser un nouvel ID de connexion lors de la migration, car l'homologue sera incapable de répondre si le pool est épuisé.
Un point de terminaison qui sélectionne un ID de connexion de longueur nulle pendant la négociation ne peut pas émettre de nouveaux ID de connexion. Un champ ID de connexion de destination de longueur nulle est utilisé dans tous les paquets envoyés vers un tel point de terminaison via n'importe quel chemin réseau.
5.1.2 Consommation et retrait des ID de connexion (Consuming and Retiring Connection IDs)
Un point de terminaison peut changer l'ID de connexion qu'il utilise pour son homologue en un autre ID de connexion disponible à tout moment pendant la connexion. Un point de terminaison consomme des ID de connexion en réponse à un homologue qui migre ; voir la section 9.5 pour plus de détails.
Un point de terminaison maintient un ensemble d'ID de connexion reçus de son homologue, dont il peut utiliser n'importe lequel lors de l'envoi de paquets. Lorsqu'un point de terminaison souhaite retirer un ID de connexion de l'utilisation, il envoie une trame RETIRE_CONNECTION_ID à son homologue. L'envoi d'une trame RETIRE_CONNECTION_ID indique que l'ID de connexion ne sera plus utilisé et demande à l'homologue de le remplacer par un nouvel ID de connexion avec une trame NEW_CONNECTION_ID.
Comme décrit dans la section 9.5, les points de terminaison limitent l'utilisation d'un ID de connexion aux paquets envoyés depuis une seule adresse locale vers une seule adresse de destination. Un point de terminaison devrait retirer un ID de connexion lorsqu'il n'utilise plus activement l'adresse locale ou de destination à laquelle l'ID de connexion était utilisé.
Un point de terminaison peut avoir besoin de cesser d'accepter des ID de connexion précédemment émis dans certaines circonstances. Un tel point de terminaison peut faire en sorte que son homologue retire des ID de connexion en envoyant une trame NEW_CONNECTION_ID avec un champ Retire Prior To accru. Le point de terminaison devrait continuer à accepter les ID de connexion précédemment émis jusqu'à ce qu'ils soient retirés par l'homologue. Si le point de terminaison ne peut plus traiter les ID de connexion indiqués, il peut fermer la connexion.
Après réception d'un champ Retire Prior To accru, l'homologue doit cesser d'utiliser les ID de connexion correspondants et les retirer en utilisant des trames RETIRE_CONNECTION_ID avant d'ajouter les ID de connexion nouvellement fournis à l'ensemble des ID de connexion actifs. Cet ordre permet à un point de terminaison de remplacer tous les ID de connexion actifs sans possibilité que l'homologue n'ait pas d'ID de connexion disponible et sans dépasser la limite définie par l'homologue dans le paramètre de transport active_connection_id_limit ; voir la section 18.2. Ne pas cesser d'utiliser les ID de connexion lorsque demandé peut entraîner un échec de connexion, car le point de terminaison émetteur pourrait ne pas être en mesure de continuer à utiliser l'ID de connexion dans la connexion active.
Un point de terminaison devrait limiter le nombre d'ID de connexion qu'il a retirés localement mais pour lesquels les trames RETIRE_CONNECTION_ID n'ont pas encore été confirmées. Un point de terminaison devrait permettre l'envoi et le suivi d'au moins le double de la valeur du paramètre de transport active_connection_id_limit en nombre de trames RETIRE_CONNECTION_ID. Un point de terminaison ne doit pas oublier un ID de connexion sans le retirer, bien qu'il puisse choisir de traiter les ID de connexion qui doivent être retirés au-delà de cette limite comme une erreur de connexion de type CONNECTION_ID_LIMIT_ERROR.
Un point de terminaison ne devrait pas émettre de mises à jour du champ Retire Prior To avant de recevoir des trames RETIRE_CONNECTION_ID qui retirent tous les ID de connexion indiqués par la valeur Retire Prior To précédente.
5.2 Correspondance des paquets aux connexions
Les paquets entrants sont classés à la réception. Les paquets peuvent être associés à une connexion existante ou, pour un serveur, peuvent créer une nouvelle connexion.
Un point de terminaison tente d'associer un paquet à une connexion existante. Si le paquet a un ID de connexion de destination de longueur non nulle correspondant à une connexion existante, QUIC traite ce paquet en conséquence. Notez que plusieurs ID de connexion peuvent être associés à une connexion ; voir la section 5.1.
Si l'ID de connexion de destination est de longueur nulle et que les informations d'adressage dans le paquet correspondent aux informations d'adressage que le point de terminaison utilise pour identifier une connexion avec un ID de connexion de longueur nulle, QUIC traite le paquet comme faisant partie de cette connexion. Un point de terminaison peut utiliser uniquement l'IP et le port de destination ou les adresses source et de destination pour l'identification, bien que cela rende la connexion fragile comme décrit dans la section 5.1.
Un point de terminaison peut envoyer une réinitialisation sans état (section 10.3) pour tout paquet qui ne peut être attribué à une connexion existante. Une réinitialisation sans état permet à l'homologue d'identifier plus rapidement quand une connexion devient inutilisable.
Les paquets qui correspondent à une connexion existante sont rejetés s'ils sont incompatibles avec l'état de cette connexion. Par exemple, les paquets sont rejetés si le paquet indique une version de protocole différente de celle de la connexion ou si la suppression de la protection des paquets échoue une fois que les clés attendues sont disponibles.
Les paquets invalides manquant de forte protection d'intégrité (tels que Initial, Retry ou Version Negotiation) peuvent être rejetés. Si le point de terminaison génère une erreur de connexion lors du traitement du contenu de ces paquets avant de découvrir une erreur, ou récupère complètement toutes les modifications apportées pendant ce traitement, l'erreur de connexion doit être générée.
5.2.1 Gestion des paquets client (Client Packet Handling)
Les paquets valides envoyés au client incluent toujours un ID de connexion de destination correspondant à une valeur sélectionnée par le client. Les clients qui sélectionnent de recevoir un ID de connexion de longueur nulle peuvent utiliser l'adresse locale et le port pour identifier une connexion. Les paquets qui ne correspondent pas à une connexion existante, sur la base de l'ID de connexion de destination ou (si cette valeur est de longueur nulle) de l'adresse IP locale et du port, sont rejetés.
En raison de la réorganisation ou de la perte de paquets, les clients peuvent recevoir des paquets de connexion chiffrés avec des clés qu'ils n'ont pas encore calculées. Les clients peuvent rejeter ces paquets ou peuvent les mettre en mémoire tampon dans l'espoir que des paquets ultérieurs leur permettent de calculer les clés.
Si le client reçoit un paquet utilisant une version différente de celle qu'il a initialement sélectionnée, il doit rejeter ce paquet.
5.2.2 Gestion des paquets serveur (Server Packet Handling)
Si le serveur reçoit un paquet indiquant une version non prise en charge et si le paquet est suffisamment grand pour initier une nouvelle connexion pour toute version prise en charge, le serveur devrait envoyer un paquet de négociation de version comme décrit dans la section 6.1. Le serveur peut limiter le nombre de paquets auxquels il répond avec un paquet de négociation de version. Le serveur doit rejeter les paquets plus petits qui spécifient une version non prise en charge.
Le premier paquet d'une version non prise en charge peut utiliser des sémantiques et encodages différents pour tout champ spécifique à la version. En particulier, différentes versions peuvent utiliser différentes clés de protection de paquet. Un serveur qui ne prend pas en charge une version particulière est peu susceptible de pouvoir déchiffrer la charge utile du paquet ou d'interpréter correctement le résultat. Les serveurs devraient répondre avec un paquet de négociation de version, à condition que le datagramme soit suffisamment long.
Les paquets avec une version prise en charge ou sans champ de version sont mis en correspondance avec une connexion en utilisant l'ID de connexion ou, pour les paquets avec un ID de connexion de longueur nulle, l'adresse locale et le port. Ces paquets sont traités en utilisant la connexion sélectionnée ; sinon, le serveur continue comme décrit ci-dessous.
Si le paquet est un paquet Initial entièrement conforme, le serveur continue la négociation (section 7). Cela engage le serveur à la version sélectionnée par le client.
Si le serveur refuse d'accepter une nouvelle connexion, il devrait envoyer un paquet Initial contenant une trame CONNECTION_CLOSE avec le code d'erreur CONNECTION_REFUSED.
Si le paquet est un paquet 0-RTT, le serveur peut mettre en mémoire tampon un nombre limité de ces paquets dans l'attente d'un paquet Initial arrivant en retard. Les clients ne peuvent pas envoyer de paquets Handshake avant de recevoir une réponse du serveur, donc le serveur devrait ignorer tout paquet de ce type.
Le serveur doit rejeter les paquets entrants dans tous les autres cas.
5.2.3 Considérations pour les équilibreurs de charge simples (Considerations for Simple Load Balancers)
Les déploiements de serveur peuvent équilibrer la charge entre les serveurs en utilisant uniquement les adresses IP et ports source et de destination. Les changements dans l'adresse IP ou le port du client peuvent entraîner le transfert de paquets vers le mauvais serveur. De tels déploiements de serveur peuvent utiliser l'une des méthodes suivantes pour maintenir la continuité de connexion lorsque l'adresse du client change.
-
Les serveurs peuvent utiliser un mécanisme hors bande pour transférer les paquets vers le serveur correct en fonction de l'ID de connexion.
-
Si les serveurs peuvent utiliser une adresse IP ou un port de serveur dédié (différent de l'adresse ou du port auquel le client s'est initialement connecté), ils peuvent utiliser le paramètre de transport preferred_address pour demander au client de déplacer la connexion vers cette adresse dédiée. Notez que les clients peuvent choisir de ne pas utiliser l'adresse préférée.
Les serveurs dans les déploiements qui n'implémentent pas de solution pour maintenir la continuité de connexion lorsque l'adresse du client change devraient utiliser le paramètre de transport disable_active_migration pour indiquer que la migration n'est pas prise en charge. Le paramètre de transport disable_active_migration n'interdit pas la migration de connexion du client après avoir agi sur le paramètre de transport preferred_address.
Les déploiements de serveur utilisant cette forme simple d'équilibrage de charge doivent éviter de créer un oracle de réinitialisation sans état ; voir la section 21.11.
5.3 Opérations sur les connexions
Ce document ne définit pas d'API pour QUIC ; au lieu de cela, il définit un ensemble de fonctions d'une connexion QUIC sur lesquelles les protocoles d'application peuvent compter. Les protocoles d'application peuvent supposer que l'implémentation de QUIC fournit une interface incluant les opérations décrites dans cette section. Les implémentations conçues pour un protocole d'application spécifique peuvent fournir uniquement les opérations utilisées par ce protocole.
Lors de l'implémentation du rôle de client, un protocole d'application peut :
- Ouvrir une connexion, ce qui démarre l'échange décrit dans la section 7 ;
- Activer Early Data (si disponible) ;
- Être informé que Early Data a été accepté ou rejeté par le serveur.
Lors de l'implémentation du rôle de serveur, un protocole d'application peut :
- Écouter les connexions entrantes, ce qui prépare l'échange décrit dans la section 7 ;
- Si Early Data est pris en charge, intégrer des données contrôlées par l'application dans le ticket de reprise TLS envoyé au client ;
- Si Early Data est pris en charge, récupérer les données contrôlées par l'application du ticket de reprise du client et accepter ou rejeter Early Data en fonction de ces informations.
Dans l'un ou l'autre rôle, un protocole d'application peut :
- Configurer une valeur minimale pour le nombre initial de flux autorisés de chaque type, comme communiqué dans les paramètres de transport (section 7.4) ;
- Contrôler l'allocation de ressources du tampon de réception en définissant des limites de contrôle de flux pour les flux et la connexion ;
- Identifier si la négociation s'est terminée avec succès ou est toujours en cours ;
- Empêcher la fermeture silencieuse de la connexion en générant des trames PING (section 19.2) ou en demandant au transport d'envoyer des trames supplémentaires avant l'expiration du délai d'inactivité (section 10.1) ;
- Fermer immédiatement (section 10.2) la connexion.
Chapitre précédent : 4. Contrôle de flux (Flow Control)
Chapitre suivant : 6. Négociation de version (Version Negotiation)