Date de publication du RFC : Janvier 2025
Auteur(s) du RFC : T. Pauly (Apple), B. Trammell (Google Switzerland), A. Brunstrom (Karlstad University), G. Fairhurst (University of Aberdeen), C. Perkins (University of Glasgow)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF taps
Première rédaction de cet article le 23 janvier 2025
Ce RFC s'inscrit dans un projet IETF datant de quelques années : formaliser davantage les services que rend la couche Transport aux applications. Le but est de permettre de développer des applications en indiquant ce qu'on veut de la couche Transport, en rentrant le moins possible dans les détails. Ce RFC décrit l'architecture générale, le RFC 9622 spécifiant quant à lui une API plus concrète.
Des API pour les applications parlant à la couche Transport, il y en a beaucoup. La plus connue est bien sûr l'interface « socket », qui a pour principal avantage d'être ancienne et très documentée, dans des livres (comme le génial Stevens) et dans d'innombrables logiciels libres à étudier (mais, par contre, la norme POSIX qui la spécifie officiellement, n'est toujours pas disponible publiquement). C'est d'ailleurs cette multiplicité d'API, donc beaucoup sont solidement installées dans le paysage, qui fait que je doute du succès du projet et de sa TSAPI (Transport Services API) : la base installée à déplacer est énorme.
Pourtant, ces API traditionnelles ont de nombreux défauts. Le RFC
cite par exemple l'incohérence : pour envoyer des données sur une
prise qui utilise du TCP en clair, on
utilise send()
mais quand
la communication est chiffrée avec TLS, c'est une autre
fonction (non normalisée). Pourtant, pour l'application, c'est la
même chose.
Toujours question cohérence, la terminologie n'est pas fixée et des termes comme flow, stream, connection et message ont des sens très différents selon les cas.
Le but de la nouvelle architecture est donc de permettre le développement d'une API (normalisée dans le RFC 9622) qui unifiera tout cela. (Notez que l'IETF, habituellement, normalise des protocoles et pas des API. Mais il y a déjà eu des exceptions à cette règle.) Un autre objectif est de permettre de mettre dans les bibliothèques du code qui autrefois était souvent dupliqué d'une application à l'autre, comme la mise en œuvre de l'algorithme des « globes oculaires heureux » (RFC 8305).
J'avais déjà présenté le projet TAPS, qui était alors nettement moins avancé, dans un exposé à la conférence MiXiT (dont voici le support).
L'architecture présentée ici est dérivée de l'analyse des protocoles de transport du RFC 8095 et de leur synthèse dans le RFC 8923. Pour les questions de sécurité, il est également conseillé de lire le RFC 8922.
L'interface pour les applications (TSAPI : Transport Services Application Programming Interface) sera fondée sur l'asynchronicité, en mode programmation par évènements. (Opinion personnelle : c'est une mauvaise idée, excessivement favorable aux langages sans vrai parallélisme, comme C. J'aurais préféré un modèle synchrone, fondé sur des fils d'exécution parallèle, plus simple pour la·e programmeur·se.)
Une des idées de base du projet est de développer une API qui soit indépendante du langage de programmation. C'est à mon avis fichu dès le départ. Outre cette question du parallélisme, la gestion des erreurs est très variable d'un langage à l'autre : codes de retour (en C), exceptions, valeurs mixtes comme en Haskell ou en Zig… L'API du RFC 9622 ne plaira donc pas à tout le monde.
La section 2 du RFC expose le modèle utilisé par l'API. D'abord, un résumé de la traditionnelle API « socket » :
SOCK_STREAM
mais, en pratique, son
utilisation nécessite de connaitre le protocole de transport utilisé),La nouvelle API généralisera ce modèle : l'application utilise l'API Transport Services (TSAPI), en indiquant les propriétés souhaitées pour la connexion, et la mise en œuvre de l'API parlera à tous les protocoles de transport (et de chiffrement) possibles, qu'ils soient placés dans le noyau ou pas. La résolution de noms est intégrée dans ce cadre, entre autres parce que certains services sous-jacents, notamment TLS, nécessitent de connaitre le nom du service auquel on accède (ce qui n'est pas le cas avec l'API socket, qui ne manipule que des adresses IP).
Pendant qu'on en parle, cette idée d'intégrer la résolution de
noms est également dans des projets comme Connect by
Name, qui vise à permettre aux applications de juste
demander connect(domain-name, port, tls=true)
et que tout soit fait proprement par l'API, notamment les fonctions
de sécurité (vérifier le nom dans le
certificat, par exemple, ce qui n'est pas
trivial à faire proprement).
On l'a dit plus haut, TSAPI est asynchrone. Le RFC estime que ce mode d'interaction avec le réseau, fondé sur des évènements, est plus fréquent et même « plus naturel ». C'est un des reproches que je ferais au projet. Je préfère le modèle d'interactions synchrone, notamment pour des langages de programmation ayant la notion de fils d'exécution séparés (les goroutines en Go, les processus en Erlang ou Elixir, les tâches d'Ada…), dans l'esprit des Communicating sequential processes (et j'en profite pour recommander le génial livre de Hoare, bien qu'il soit assez abstrait et d'un abord austère). Rien n'est naturel en programmation (ou ailleurs : « la seule interface utilisateur naturelle est le téton ; tout le reste est appris ») et le RFC se débarrasse un peu vite du choix effectué et ne cherche pas vraiment à le justifier.
Donc, l'application qui veut, par exemple, recevoir des données, va appeler l'API pour demander une lecture et l'API préviendra l'application quand des données seront prêtes. Au contraire, dans un modèle synchrone, le fil qui veut lire sera bloqué tant qu'il n'y aura rien à lire, les autres fils continuant leur travail.
Dans l'API socket, on peut avoir des messages séparés lorsqu'on utilise UDP mais, avec TCP, c'est forcément une suite d'octets, sans séparation des éventuels messages. Chaque protocole applicatif doit donc, s'il en a besoin, inventer sa méthode de découpage en messages : DNS et EPP indiquent la longueur du message avant d'envoyer le message, HTTP/1 l'indique dans l'en-tête mais a un mécanisme de découpage fondé sur des délimiteurs (cf. RFC 9112, sections 6.1 et 7), HTTP/2 et HTTP/3 ont leurs ruisseaux (une requête par ruisseau), TLS utilise également une indication de longueur. Au contraire, avec TSAPI, il y a toujours un découpage en messages, qui seront encodés en utilisant les capacités du protocole de transport sous-jacent (par un mécanisme nommé cadreur - framer).
TSAPI permet des choses qui n'étaient pas possibles avec les API classiques, comme d'avoir plusieurs paires d'adresses IP entre les machines qui communiquent, avec changement de la paire utilisée pendant une connexion.
Un des intérêts de cette nouvelle API est de permettre aux applications de fonctionner de manière plus déclarative et moins impérative. Au lieu de sélectionner tel ou tel protocole précis, l'application déclare ses exigences, et TSAPI se débrouillera pour les lui fournir (section 3.1).
Bon, c'est bien joli, de vouloir que les applications ne connaissent pas les protocoles (ce qui permet de les faire évoluer plus facilement) mais, quand même, parfois, il existe des fonctions très spécifiques d'un protocole particulier qu'il serait bien sympa d'utiliser. La section 3.2 de notre RFC couvre donc cette question. Par exemple, l'option User Timeout de TCP (RFC 5482) est bien pratique, même si elle est spécifique à TCP. Donner accès à ces spécificités soulève toutefois un problème : parfois, l'application souhaite une certaine fonction, et peut donc s'en passer, et parfois elle l'exige et la connexion doit échouer si elle n'est pas disponible pour un certain protocole. L'API devra donc distinguer les deux cas.
Une fois que l'application a indiqué ses préférences et ses exigences, l'API devra sélectionner un protocole de transport. Parfois, plusieurs protocoles conviendront. Si l'application demande juste un transport fiable d'une suite d'octets, TCP, SCTP et QUIC conviendront tous les trois (alors qu'UDP serait exclu).
Comme rien n'est gratuit en ce bas monde, le progrès de l'abstraction, et une relative indépendance de l'application par rapport aux particularités des protocoles de transport, ont un inconvénient : le système devient plus complexe. Les développeureuses d'application lorsqu'il faut déboguer, et les administrateurices système qui vont installer (et essuyer les plâtres !) les applications vont avoir des difficultés nouvelles. Des fonctions de déboguage devront donc être intégrées. (Personnellement, je pense aussi à la nécessité que la bibliothèque qui mette en œuvre TSAPI coopère au déboguage pour les protocoles chiffrés, qui ne permettront pas tellement d'utiliser Wireshark. Voir mon exposé à Capitole du Libre.)
Et enfin, après ces généralités, passons aux concepts qui devront être visibles par l'application, rapprochons-nous de la future API (section 4 du RFC). D'abord, la Connexion. Non, ce n'est pas une erreur d'avoir capitalisé le mot. Le RFC utilise la même méthode pour montrer qu'il s'agit d'un concept abstrait, qui ne correspond pas forcément parfaitement aux connexions du protocole de transport (pour ceux qui ont ce concept). La Connexion est donc au cœur de l'API Transport Services. Elle se fait entre deux points, le local et le distant, chacun identifié par un Endpoint Identifier. Elle a un certain nombre de propriétés, qui peuvent s'appliquer avant (et influenceront la sélection du protocole de transport, et/ou des machines auxquelles ont se connecte), juste avant ou pendant la Connexion, par exemple l'utilisation du 0-RTT (RFC 9622, section 6.2.5) ou l'exigence du chiffrement (cf. RFC 8922), mais il y a aussi des propriétés pour chaque message individuel. Ensuite, on va :
Close
ou salement avec Abort
).
Un écoutant va créer une Pré-Connexion et attendre les demandes, ce
n'est pas lui qui va établir les connexions. L'entité active, elle,
va se connecter (fonctions Listen
et
Initiate
). Et le
pair-à-pair ? Il sera également possible via
la fonction Rendezvous
. Ce sera à l'API de
gérer les services comme STUN (RFC 8489),
souvent indispensables en pair-à-pair.
Le transfert de données a les classiques fonctions
Send
et Receive
(qui n'est
pas synchrone, rappelons-le, donc qui ne bloque pas). Mais il y a
aussi un nouveau concept, le cadreur (traduction peu imaginative de
framer). C'est la couche logicielle qui va ajouter et/ou modifier les
données du message pour mettre en œuvre des exigences du protocole,
par exemple indiquer les frontières entre messages dans un protocole
de transport qui n'a pas ce concept (comme TCP). Ainsi, les
protocoles EPP (RFC 5734) et
DNS (RFC 7766) nécessitent que chaque message soit
préfixé d'un seizet indiquant sa longueur. Un
cadreur peut faire cela automatiquement pour chaque message,
simplifiant la tâche de l'application (par exemple la bibliothèque
standard d'Erlang a cette
option - cf. packet
).
Enfin, un peu de sécurité et de vie privée (section 6). D'abord, les implémentations devront faire attention aux risques pour la vie privée si on réutilise l'état des connexions passées. C'est le cas des sessions TLS (RFC 8446, section 2.2) ou des gâteaux HTTP (RFC 6265), qui peuvent permettre de relier deux connexions entre elles, lien qu'on ne souhaite peut-être pas.
Et puis, en sécurité, le RFC rappelle qu'on ne doit pas se replier sur des solutions non sécurisées si la technique sécurisée échoue. Ainsi, si une application demande du chiffrement, mais que la connexion chiffrée échoue, il ne faut pas se rabattre sur une connexion en clair.
Notez que l'ensemble du projet TAPS a bénéficié de divers financements publics, par exemple de l'UE.
Une opinion personnelle sur ce projet ? Outre des critiques sur tel ou tel choix, comme l'asynchronisme, je suis malheureusement sceptique quant aux chances de réussite. Les API actuelles, avec tous leurs défauts, sont solidement installées, et dans le meilleur des cas, il faudra de nombreuses années pour que les formations s'adaptent et enseignent une API comme TSAPI.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)