Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

Les RFC (Request For Comments) sont les documents de référence de l'Internet. Produits par l'IETF pour la plupart, ils spécifient des normes, documentent des expériences, exposent des projets...

Leur gratuité et leur libre distribution ont joué un grand rôle dans le succès de l'Internet, notamment par rapport aux protocoles OSI de l'ISO organisation très fermée et dont les normes coûtent cher.

Je ne tente pas ici de traduire les RFC en français (un projet pour cela existe mais je n'y participe pas, considérant que c'est une mauvaise idée), mais simplement, grâce à une courte introduction en français, de donner envie de lire ces excellents documents. (Au passage, si vous les voulez présentés en italien...)

Le public visé n'est pas le gourou mais l'honnête ingénieur ou l'étudiant.


RFC 9309: Robots Exclusion Protocol

Date de publication du RFC : Septembre 2022
Auteur(s) du RFC : M. Koster, G. Illyes, H. Zeller, L. Sassman (Google)
Chemin des normes
Première rédaction de cet article le 14 septembre 2022


Quand vous gérez un serveur Internet (pas forcément un serveur Web) prévu pour des humains, une bonne partie du trafic, voire la majorité, vient de robots. Ils ne sont pas forcément malvenus mais ils peuvent être agaçants, par exemple s'ils épuisent le serveur à tout ramasser. C'est pour cela qu'il existe depuis longtemps une convention, le fichier robots.txt, pour dire aux robots gentils ce qu'ils peuvent faire et ne pas faire. La spécification originale était très limitée et, en pratique, la plupart des robots comprenait un langage plus riche que celui du début. Ce nouveau RFC documente plus ou moins le format actuel.

L'ancienne spécification, qui date de 1996, est toujours en ligne sur le site de référence (qui ne semble plus maintenu, certaines informations ont des années de retard). La réalité des fichiers robots.txt d'aujourd'hui est différente, et plus riche. Mais, comme souvent sur l'Internet, c'est assez le désordre, avec différents robots qui ne comprennent pas la totalité du langage d'écriture des robots.txt. Peut-être que la publication de ce RFC aidera à uniformiser les choses.

Donc, l'idée de base est la suivante : le robot qui veut ramasser des ressources sur un serveur va d'abord télécharger un fichier situé sur le chemin /robots.txt. (Au passage, si cette norme était faite aujourd'hui, on utiliserait le /.well-known du RFC 8615.) Ce fichier va contenir des instructions pour le robot, lui disant ce qu'il peut récupérer ou pas. Ces instructions concernent le chemin dans l'URI (et robots.txt n'a donc de sens que pour les serveurs utilisant ces URI du RFC 3986, par exemple les serveurs Web). Un exemple très simple serait ce robots.txt :

User-Agent: *
Disallow: /private
  

Il dit que, pour tous les robots, tout est autorisé sauf les chemins d'URI commençant par /private. Un robot qui suit des liens (RFC 8288) doit donc ignorer ceux qui mènent sous /private.

Voyons maintenant les détails pratiques (section 2 du RFC). Un robots.txt est composé de groupes, chacun s'appliquant à un robot ou un groupe de robots particulier. Les groupes sont composés de règles, chaque règle disant que telle partie de l'URI est interdite ou autorisée. Par défaut, tout est autorisé (même chose s'il n'y a pas de robots.txt du tout, ce qui est le cas de ce blog). Un groupe commence par une ligne User-Agent qui va identifier le robot (ou un astérisque pour désigner tous les robots) :

User-Agent: GoogleBot
  

Le robot va donc chercher le ou les groupes qui le concernent. En HTTP, normalement, l'identificateur que le robot cherche dans le robots.txt est une sous-chaine de l'identificateur envoyé dans le champ de l'en-tête HTTP User-Agent (RFC 9110, section 10.1.5).

Le robot doit combiner tous les groupes qui s'appliquent à lui, donc on voit parfois plusieurs groupes avec le même User-Agent.

Le groupe est composé de plusieurs règles, chacune commençant par Allow ou Disallow (notez que la version originale de la spécification n'avait pas Allow). Si plusieurs règles correspondent, le robot doit utiliser la plus spécifique, c'est-à-dire celle dont le plus d'octets correspond. Par exemple, avec :

Disallow: /private
Allow: /private/notcompletely
  

Une requête pour /private/notcompletely/foobar sera autorisée. L'ordre des règles ne compte pas (mais certains robots sont bogués sur ce point). C'est du fait de cette notion de correspondance la plus spécifique qu'on ne peut pas compiler un robots.txt en une simple expression rationnelle, ce qui était possible avec la spécification originelle. Si une règle Allow et une Disallow correspondent, avec le même nombre d'octets, l'accès est autorisé.

Le robot a le droit d'utiliser des instructions supplémentaires, par exemple pour les sitemaps.

En HTTP, le robot peut rencontrer une redirection lorsqu'il essaie de récupérer le robots.txt (code HTTP 301, 302, 307 ou 308). Il devrait dans ce cas la suivre (mais pas infinement : le RFC recommande cinq essais au maximum). S'il n'y a pas de robots.txt (en HTTP, code de retour 404), tout est autorisé (c'est le cas de mon blog). Si par contre il y a une erreur (par exemple 500 en HTTP), le robot doit attendre et ne pas se croire tout permis. Autre point HTTP : le robot peut suivre les instructions de mémorisation du robots.txt données, par exemple, dans le champ Cache-control de l'en-tête.

Avant de passer à la pratique, un peu de sécurité. D'abord, il faut bien se rappeler que le respect du robots.txt dépend de la bonne volonté du robot. Un robot malveillant, par exemple, ne tiendra certainement pas compte du robots.txt. Mais il peut y avoir d'autres raisons pour ignorer ces règles. Par exemple, l'obligation du dépôt légal fait que la BNF annonce explicitement qu'elle ignore ce fichier. (Et un programme comme wget a une option, -e robots=off, pour débrayer la vérification du robots.txt.) Bref, un robots.txt ne remplace pas les mesures de sécurité, par exemple des règles d'accès aux chemins définies dans la configuration de votre serveur HTTP. Le robots.txt peut même diminuer la sécurité, en indiquant les fichiers « intéressants » à un éventuel attaquant.

Passons maintenant à la pratique. On trouve plein de mises en œuvre de robots.txt un peu partout mais en trouver une parfaitement conforme au RFC est bien plus dur. Disons-le clairement, c'est le bordel, et l'auteur d'un robots.txt ne peut jamais savoir comment il va être interprété. Il ne faut donc pas être trop subtil dans son robots.txt et en rester à des règles simples. Du côté des robots, on a un problème analogue : bien des robots.txt qu'on rencontre sont bogués. La longue période sans spécification officielle est largement responsable de cette situation. Et tout n'est pas clarifié par le RFC. Par exemple, la grammaire en section 2.2 autorise un Disallow ou un Allow à être vide, mais sans préciser clairement la sémantique associée.

Pour Python, il y a un module standard, mais qui est loin de suivre le RFC. Voici un exemple d'utilisation :

import urllib.robotparser
import sys

if len(sys.argv) != 4:
    raise Exception("Usage: %s robotstxt-file user-agent path" % sys.argv[0])
input = sys.argv[1]
ua = sys.argv[2]
path = sys.argv[3]
parser = urllib.robotparser.RobotFileParser()
parser.parse(open(input).readlines())
if parser.can_fetch(ua, path):
    print("%s can be fetched" % path)
else:
    print("%s is denied" % path)

Et, ici, on se sert de ce code :

% cat ultra-simple.txt 
User-Agent: *
Disallow: /private

% python test-robot.py ultra-simple.txt foobot /public/test.html         
/public/test.html can be fetched

% python test-robot.py ultra-simple.txt foobot /private/test.html
/private/test.html is denied

Mais ce module ne respecte pas la précédence des règles :

% cat precedence.txt
User-Agent: *
Disallow: /
Allow: /bar.html

% python3 test-robot.py precedence.txt foobot /bar.html
/bar.html is denied

En application de la règle la plus spécifique, /bar.html aurait dû être autorisé. Si on inverse Allow et Disallow, on obtient le résultat attendu. Mais ce n'est pas normal, l'ordre des règles n'étant normalement pas significatif. Autre problème du module Python, il ne semble pas gérer les jokers, comme l'exemple *.gif$ du RFC. Il existe des modules Python alternatifs pour traiter les robots.txt mais aucun ne semble vraiment mettre en œuvre le RFC.

La situation n'est pas forcément meilleure dans les autres langages de programmation. Essayons avec Elixir. Il existe un module pour cela. Écrivons à peu près le même programme qu'en Python :

usage = "Usage: test robotstxtname useragent path"
filename =
  case Enum.at(System.argv(), 0) do
    nil -> raise RuntimeError, usage
    other -> other
  end
content =
  case File.read(filename) do
    {:ok, data} -> data
    {:error, reason} -> raise RuntimeError, "#{filename}: #{reason}"
  end
ua =
  case Enum.at(System.argv(), 1) do
    nil -> raise RuntimeError, usage
    other -> other
  end
statuscode = 200
{:ok, rules} = :robots.parse(content, statuscode) 
path =
  case Enum.at(System.argv(), 2) do
    nil -> raise RuntimeError, usage
    other -> other
  end
IO.puts(
  case :robots.is_allowed(ua, path, rules) do
    true -> "#{path} can be fetched"
    false -> "#{path} can NOT be fetched"
  end)
  

Et testons-le :

% mix run test-robot.exs ultra-simple.txt foobot /public/test.html   
/public/test.html can be fetched

% mix run test-robot.exs ultra-simple.txt foobot /private/test.html  
/private/test.html can NOT be fetched
  

Il semble avoir moins de bogues que le module Python mais n'est quand même pas parfait.

Comme dit plus haut, le robots.txt n'est pas réservé au Web. Il est utilisé par exemple pour Gemini. Ainsi, le robot de collecte Lupa lit les robots.txt et ne va pas ensuite récupérer les URI interdits. En septembre 2022, 11 % des capsules Gemini avaient un robots.txt.


Téléchargez le RFC 9309


L'article seul

RFC 9297: HTTP Datagrams and the Capsule Protocol

Date de publication du RFC : Août 2022
Auteur(s) du RFC : D. Schinazi (Google), L. Pardue (Cloudflare)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF masque
Première rédaction de cet article le 8 septembre 2022


Ce RFC est le premier du groupe de travail MASQUE qui développe des techniques pour relayer du trafic IP. Dans ce RFC, il s'agit de faire passer des datagrammes sur HTTP.

Pourquoi, sur HTTP ? Parce que c'est le seul protocole dont on est raisonnablement sûr qu'il passera partout. En bâtissant un service de datagrammes sur HTTP (et non pas directement sur IP), on arrivera à le faire fonctionner à tous les endroits où HTTP fonctionne.

En fait, ce RFC décrit deux mécanismes assez différents, mais qui visent le même but, la transmission de datagrammes sur HTTP. Le premier mécanisme est le plus simple, utiliser HTTP juste pour lancer une session qui utilisera les datagrammes de QUIC, normalisés dans le RFC 9221. (QUIC seul ne passe pas partout, d'où la nécessité d'insérer une couche HTTP.) Ce premier mécanisme dépend de QUIC donc ne marche qu'avec HTTP/3 (RFC 9114). Un second mécanisme est normalisé, dans notre RFC ; nommé Capsule, il permet de faire passer des datagrammes (ou à peu près) sur HTTP/2 (RFC 9113) et HTTP/1 (RFC 9112). Capsule est plus général, mais moins efficace.

Ces mécanismes ne sont pas vraiment prévus pour des utilisations par des applications, mais plutôt pour des extensions à HTTP (RFC 9110, section 16). Par exemple, le service CONNECT (RFC 9110, section 9.3.6) pourrait être doublé par un service équivalent, mais utilisant des datagrammes, au lieu du transfert fiable que fait CONNECT (cf. RFC 9298). Même chose pour les Web sockets du RFC 6455.

La section 2 de notre RFC explique de quels datagrammes on parle. Il s'agit de paquets de données dont l'ordre d'arrivée et l'acheminement ne sont pas garantis, et qui vont sans doute consommer moins de ressources. Sur HTTP/3 (qui utilise QUIC), ils vont utiliser les trames QUIC de type DATAGRAM (RFC 9221). Ce sont les « meilleurs » datagrammes HTTP, ceux qui collent le plus à la sémantique des datagrammes. Sur HTTP/2, tout acheminement de données est garanti. Si on veut faire des datagrammes, il va falloir utiliser le protocole Capsule, décrit dans la section 3. (Notez que HTTP/2 ne garantit pas l'ordre d'arrivée si les datagrammes sont transportés dans des ruisseaux différents.) Et pour HTTP/1 ? Le protocole ne convient guère puisqu'il garantit l'ordre d'arrivée et l'acheminement, justement les propriétés auxquelles on était prêt à renoncer. Là aussi, on se servira de Capsule.

HTTP utilise différentes méthodes pour faire ses requêtes (les deux plus connues sont GET et POST). Les datagrammes ne sont utilisables qu'avec des méthodes qui les acceptent explicitement. Donc, pas de GET ni de POST, plutôt du CONNECT.

Sur HTTP/3, le champ de données du datagramme QUIC aura le format :

HTTP/3 Datagram {
     Quarter Stream ID (i),
     HTTP Datagram Payload (..),
   }
  

Le quarter stream ID est l'identificateur du ruisseau QUIC du client où a été envoyée la requête HTTP, divisé par 4 (ce qu'on peut toujours faire, vu la façon dont sont générés ces identificateurs, RFC 9000, section 2.1).

Ah, et comme les datagrammes ne sont pas acceptés par défaut en HTTP/3, il faudra envoyer au début de la session un paramètre SETTINGS_H3_DATAGRAM (enregistré à l'IANA).

Les capsules, maintenant. Inutiles en HTTP/3, elles sont la seule façon de faire du datagramme en HTTP/1 et HTTP/2. Le protocole Capsule permet d'envoyer des données encodées en TLV sur une connexion HTTP. On indique qu'on va l'utiliser, en HTTP/1 avec le champ Upgrade: (RFC 9110, section 7.8) et en HTTP/2 avec un CONNECT étendu (cf. RFC 8441). Les upgrade tokens (les identificateurs de protocoles utilisés, enregistrés à l'IANA) sont décrits dans la section 16.7 du RFC 9110.

Le format des capsules est décrit en section 3.2 :

Capsule {
     Capsule Type (i),
     Capsule Length (i),
     Capsule Value (..),
   }
  

Les différents types possibles de capsules figurent dans un registre IANA. Une fois le protocole Capsule sélectionné via un upgrade token, on passe du HTTP classique au protocole Capsule. Un premier type de capsule est DATAGRAM (type 0) dont le nom indique bien la sémantique. D'autres types pourront être ajoutés en suivant la procédure « spécification nécessaire » du RFC 8126, sauf les valeurs basses du type (de 0 à 63) qui exigent une action de normalisation, ou bien une approbation par l'IESG.

Si on utilise Capsule, la requête HTTP est accompagnée du champ Capsule-Protocol (un champ structuré, cf. RFC 8941), champ désormais enregistré à l'IANA.

Il existe plusieurs mises en œuvre de ces datagramms HTTP. En logiciel libre, on a :

Notez que le protocole Capsule n'est a priori pas accessible au code JavaScript chargé dans le navigateur Web (il faut pouvoir accéder aux upgrade tokens). Mais rappelez-vous que tout ce mécanisme de datagrammes sur HTTP est conçu pour des extensions de HTTP, pas pour l'application finale.


Téléchargez le RFC 9297


L'article seul

RFC 9293: Transmission Control Protocol (TCP)

Date de publication du RFC : Août 2022
Auteur(s) du RFC : W. Eddy (MTI Systems)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tcpm
Première rédaction de cet article le 20 août 2022


Le protocole de transport TCP est l'un des piliers de l'Internet. La suite des protocoles Internet est d'ailleurs souvent appelée TCP/IP, du nom de ses deux principaux protocoles. Ce nouveau RFC est la norme technique de TCP, remplaçant l'antique RFC 793, qui était vieux de plus de quarante ans, plus vieux que la plupart des lecteurices de ce blog. Il était temps de réviser et de rassembler en un seul endroit tous les détails sur TCP.

TCP est donc un protocole de transport. Rappelons brièvement ce qu'est un protocole de transport et à quoi il sert. L'Internet achemine des paquets IP de machine en machine. IP ne garantit pas leur arrivée : les paquets peuvent être perdus (par exemple parce qu'un routeur n'avait plus de place dans ses files d'attente), peuvent être dupliqués, un paquet peut passer devant un autre, pourtant envoyé avant, etc. Pour la grande majorité des applications, ce n'est pas acceptable. La couche de transport est donc chargée de remédier à cela. Première (en partant du bas) couche à être de bout en bout (les routeurs et autres équipements intermédiaires n'y participent pas, ou plus exactement ne devraient pas y participer), elle se charge de suivre les paquets, de les remettre dans l'ordre, et de demander aux émetteurs de ré-émettre si un paquet s'est perdu. C'est donc un rôle crucial, puisqu'elle évite aux applications de devoir gérer ces opérations très complexes. (Certaines applications, par exemple de vidéo, n'ont pas forcément besoin que chaque paquet arrive, et n'utilisent pas TCP.) Le RFC 8095 contient une comparaison détaillée des protocoles de transport de l'IETF.

Avant de décrire TCP, tel que spécifié dans ce RFC 9293, un petit mot sur les normes techniques de l'Internet. TCP avait originellement été normalisé dans le RFC 761 en 1980 (les couches 3 - IP et 4 étaient autrefois mêlées en une seule). Comme souvent sur l'Internet, le protocole existait déjà avant sa normalisation et était utilisé. La norme avait été rapidement révisée dans le RFC 793 en 1981. Depuis, il n'y avait pas eu de révision générale, et les RFC s'étaient accumulés. Comprendre TCP nécessitait donc de lire le RFC 793 puis d'enchainer sur plusieurs autres. Un RFC, le RFC 7414, avait même été écrit dans le seul but de fournir un guide de tous ces RFC à lire. Et il fallait également tenir compte de l'accumulation d'errata. Désormais, la situation est bien plus simple, tout TCP a été consolidé dans un RFC central, notre RFC 9293. (Notez que ce travail de consolidation a été aussi fait pour HTTP et SMTP, qui ne sont plus décrits par les RFC d'origine mais par des versions à jour, mais pas pour le DNS, qui reste le principal ancien protocole qui aurait bien besoin d'une remise à jour complète des documents qui le spécifient.) Ne vous faites toutefois pas d'illusion : malgré ses 114 pages, ce RFC 9293 ne couvre pas tout, et la lecture d'autres RFC reste nécessaire. Notamment, TCP offre parfois des choix multiples (par exemple pour les algorithmes de contrôle de la congestion, une partie cruciale de TCP), qui ne sont pas décrits intégralement dans la norme de base. Voyez par exemple le RFC 7323, qui n'a pas été intégré dans le RFC de base, et reste une extension optionnelle.

Attaquons-nous maintenant à TCP. (Bien sûr, ce sera une présentation très sommaire, TCP est forcément plus compliqué que cela.) Son rôle est de fournir aux applications un flux d'octets fiable et ordonné : les octets que j'envoie à une autre machine arriveront dans l'ordre et arriveront tous (ou bien TCP signalera que la communication est impossible, par exemple en cas de coupure du câble). TCP découpe les données en segments, chacun voyageant dans un datagramme IP. Chaque octet est numéroté (attention : ce sont bien les octets et pas les segments qui sont numérotés) et cela permet de remettre dans l'ordre les paquets, et de détecter les pertes. En cas de perte, on retransmet. Le flux d'octets est bidirectionnel, la même connexion TCP permet de transmettre dans les deux sens. TCP nécessite l'établissement d'une connexion, donc la mémorisation d'un état, par les deux pairs qui communiquent. Outre les tâches de fiabilisation des données (remettre les octets dans l'ordre, détecter et récupérer les pertes), TCP fournit du démultiplexage, grâce aux numéros de port. Ils permettent d'avoir plusieurs connexions TCP entre deux machines, qui sont distinguées par ces numéros de port (un pour la source et un pour la destination) dans l'en-tête TCP. Le bon fonctionnement de TCP nécessite d'édicter un certain nombre de règles et le RFC les indique avec MUST-n où N est un entier. Par exemple, MUST-2 et MUST-3 indiqueront que la somme de contrôle est obligatoire, aussi bien en envoi qu'en vérification à la réception.

Après ces concepts généraux, la section 3 de notre RFC rentre dans les détails concrets, que je résume ici. D'abord, le format de cet en-tête que TCP ajoute derrière l'en-tête IP et devant les données du segment. Il inclut les numéros de port, le numéro de séquence (le rang du premier octet dans les données), celui de l'accusé de réception (le rang du prochain octet attendu), un certain nombre de booléens (flags, ou bits de contrôle, qui indiquent, par exemple, s'il s'agit d'une connexion déjà établie, ou pas encore, ou la fin d'une connexion), la taille de la fenêtre (combien d'octets peuvent être envoyés sans accusé de réception, ce qui permet d'éviter de surcharger le récepteur), une somme de contrôle, et des options, de taille variable (l'en-tête contient un champ indiquant à partir de quand commencent les données). Une option de base est MSS (Maximum Segment Size) qui indique quelle taille de segment on peut gérer. Il existe d'autres options comme les accusés de réception sélectifs du RFC 2018 ou comme le facteur multiplicatif de la taille de fenêtre du RFC 7323.

Avant d'expliquer la dynamique de TCP, le RFC définit quelques variables indispensables, qui font partie de l'état de la connexion TCP. Pour l'envoi de données, ce sont par exemple SND.UNA (ces noms sont là pour comprendre le RFC, mais un programme qui met en œuvre TCP peut évidemment nommer ces variables comme il veut), qui désigne le numéro de séquence du début des données envoyées, mais qui n'ont pas encore fait l'objet d'un accusé de réception, ou SND.NXT, le numéro de séquence du début des données pas encore envoyées ou encore SND.WND, la taille de la fenêtre d'envoi. Ainsi, on ne peut pas envoyer d'octets dont le rang serait supérieur à SND.UNA + SND.WND, il faut attendre des accusés de réception (qui vont incrémenter SND.UNA) ou une augmentation de la taille de la fenêtre. Pour la réception, on a RCV.NXT, le numéro de séquence des prochains paquets si tout est normal, et RCV.WND, la taille de la fenêtre de réception.

TCP est un protocole à état et il a donc une machine à états, décrite en section 3.3.2. Parmi les états, LISTEN, qui indique un TCP en train d'attendre un éventuel pair, ESTAB (ou ESTABLISHED), où TCP envoie et reçoit des données, et plusieurs états qui apparaissent lors de l'ouverture ou de la fermeture d'une connexion.

Et les numéros de séquence ? Un point important de TCP est qu'on ne numérote pas les segments mais les octets. Chaque octet a son numéro et les accusés de réception référencent ces numéros. On n'accuse évidemment pas réception de chaque octet individuel. Les accusés de réception sont cumulatifs ; ils désignent un octet et, implicitement, tous les octets précédents. Quand un récepteur prévient qu'il a bien reçu l'octet N, cela veut dire que tous ceux avant N ont aussi été reçus.

Les numéros ne partent pas de 1 (ni de zéro), ils sont relatifs à un ISN (Initial Sequence Number, rappelez-vous qu'il y a un glossaire, en section 4 du RFC) qui est choisi aléatoirement (pour des raisons de sécurité, cf. RFC 6528) au démarrage de la session. Des logiciels comme Wireshark savent cela et peuvent calculer puis afficher des numéros de séquence qui partent de 1, pour faciliter la lecture.

Les numéros de séquence sont stockés sur 32 bits et il n'y a donc que quatre milliards (et quelques) valeurs possibles. C'était énorme quand TCP a été créé mais cela devient de plus en plus petit, relativement aux capacités des réseaux modernes. Comme TCP repart de zéro quand il atteint le numéro de séquence maximum, il y a un risque qu'un « vieux » paquet soit considéré comme récent. À 1 Gb/s, cela ne pouvait se produire qu'au bout de 34 secondes. Mais à 100 Gb/s, il suffit d'un tiers de seconde ! Les extensions du RFC 7323 deviennent alors indispensables.

Tout le monde sait bien que TCP est un protocole orienté connexion. Cela veut dire qu'il faut pouvoir créer, puis supprimer, les connexions. La section 3.5 de notre RFC décrit cet établissement de connexion, via la fameuse « triple poignée de mains ». En gros, un des TCP (TCP n'est pas client-serveur, et la triple poignée de mains marche également quand les deux machines démarrent la connexion presque en même temps) envoie un paquet ayant l'option SYN (pour synchronization), le second répond avec un paquet ayant les options ACK (accusé de réception du SYN) et SYN, le premier TCP accuse réception. Avec trois paquets, les deux machines sont désormais d'accord qu'elles sont connectées. En d'autres termes (ici, machine A commence) :

  • Machine A ⇒ Machine B : je veux te parler,
  • B ⇒ A : j'ai noté que tu voulais me parler et je veux te parler aussi,
  • A ⇒ B : j'ai noté que tu voulais me parler.

Pour mettre fin à la connexion, un des deux TCP envoie un paquet ayant l'option FIN (pour finish), auquel l'autre répond de même.

Conceptuellement, TCP gère un flux d'octets sans séparation. TCP n'a pas la notion de message. Si une application envoie (en appelant print, write ou autre fonction du langage de programmation utilisé) les octets "AB" puis "CD", TCP transmettra puis livrera à l'application distante les quatre octets "ABCD" dans l'ordre, sans indiquer qu'il y avait eu deux opérations d'écriture sur le réseau. Mais, pour envoyer les données, TCP doit les segmenter en paquets IP distincts. La fragmentation IP n'étant pas bonne pour les performances, et étant peu fiable de toute façon, l'idéal est que ces paquets IP aient une taille juste inférieure à la MTU du chemin. Cela nécessite de découvrir cette MTU (section 3.7.2), ou bien d'adopter la solution paresseuse de faire des paquets de 1 280 octets (la MTU minimum, en IPv6). Comme la solution paresseuse ne serait pas optimale en performances, les mises en œuvre de TCP essaient toutes de découvrir la MTU du chemin. Cela peut se faire avec la méthode PMTUD, décrite dans les RFC 1191 et RFC 8201, mais qui a l'inconvénient de nécessiter qu'ICMP ne soit pas bloqué. Or, beaucoup de middleboxes programmées avec les pieds, ou configurées par des incompétents, bloquent ICMP, empêchant le fonctionnement de PMTUD. Une alternative est PLPMTUD, normalisée dans le RFC 4821, qui ne dépend plus d'ICMP.

Une fois la connexion établie, on peut envoyer des données. TCP va les couper en segments, chaque segment voyageant dans un paquet IP.

Une mise en œuvre de TCP n'envoie pas forcément immédiatement les octets qu'on lui a confiés. Envoyer des petits paquets n'est pas efficace : s'il y a peu de données, les en-têtes du paquet forment la majorité du trafic et, de toute façon, le goulet d'étranglement dans le réseau n'est pas forcément lié au nombre d'octets, il peut l'être au nombre de paquets. Un des moyens de diminuer le nombre de paquets contenant peu d'octets est l'algorithme de Nagle. Le principe est simple : quand TCP reçoit un petit nombre d'octets à envoyer, il ne les transmet pas tout de suite mais attend un peu pour voir si l'application ne va pas lui confier des octets supplémentaires. Décrit dans le RFC 896, et recommandé dans le RFC 1122, cet algorithme est aujourd'hui présent dans la plupart des mises en œuvres de TCP. Notre RFC 9293 le recommande, mais en ajoutant que les applications doivent pouvoir le débrayer. C'est notamment indispensable pour les applications interactives, qui n'aiment pas les délais, même courts. (Sur Unix, cela se fait avec l'option TCP_NODELAY passée à setsockopt.)

Un des rôles les plus importants de TCP est de lutter contre la congestion. Il ne faut pas envoyer le plus d'octets possible, ou alors on risque d'écrouler le réseau (RFC 2914). Plusieurs mécanismes doivent être déployés : démarrer doucement (au début de la connexion, TCP ne connait pas le débit que peut supporter le réseau et doit donc être prudent), se calmer rapidement si on constate que des paquets sont perdus (cela peut être un signe de congestion en aval), etc. Ces mesures pour éviter la congestion sont absolument obligatoires (comme tous les biens communs, l'Internet est vulnérables aux parasites, un TCP égoïste qui ne déploierait pas ces mesures anti-congestion serait un mauvais citoyen du réseau). Elles sont décrites plus précisément dans les RFC 1122, RFC 2581, RFC 5681 et RFC 6298. Notez qu'ils énoncent des principes, mais pas forcément les algorithmes exacts à utiliser, ceux-ci pouvant évoluer (et ils l'ont fait depuis la sortie du RFC 793, voir les RFC 5033 et RFC 8961).

Un point qui surprend souvent les programmeur·ses qui écrivent des applications utilisant TCP est que rien n'indique qu'une connexion TCP est coupée, par exemple si un câble est sectionné. C'est seulement lorsqu'on essaie d'écrire qu'on l'apprendra (ce comportement est une conséquence de la nature sans connexion d'IP ; pourquoi prévenir d'une coupure alors qu'IP va peut-être trouver un autre chemin ?). Si on veut être mis au courant tout de suite, on peut utiliser des keep-alives, des segments vides qui seront envoyés de temps en temps juste pour voir s'ils arrivent. Cette pratique est contestée (entre autre parce qu'elle peut mener à abandonner une connexion qui n'est que temporairement coupée, et aussi parce que, même si les keep-alives passent, cela ne garantit pas que la prochaine écriture de données passera). Notre RFC demande donc que les keep-alives soient désactivés par défaut, et que l'application puisse contrôler leur activation (sur Unix, c'est l'option SO_KEEPALIVE à utiliser avec setsockopt).

Pendant les quarante ans écoulés depuis la sortie du RFC 793, bien des choses ont changé. Des options ont été ajoutées mais on a vu aussi certaines options qui semblaient être des bonnes idées être finalement abandonnées. C'est le cas du mécanisme de données urgentes, qui n'a jamais vraiment marché comme espéré. Une mise en œuvre actuelle de TCP doit toujours le gérer, pour pouvoir parler avec les anciennes, mais on ne peut pas compter dessus (RFC 6093).

On a parlé plusieurs fois de l'utilisation de TCP par les applications. Pour cela, il faut que TCP offre une API à ces applications. Notre RFC ne décrit pas d'API concrète (cela dépend de toute façon du langage de programmation, et sans doute du système d'exploitation). Il se contente d'une description fonctionnelle de l'interface : les services qu'elle doit offrir à l'application. On y trouve :

  • L'ouverture de la connexion, en indiquant l'adresse et le port distant, mais aussi l'adresse locale (MUST-43), pour pouvoir choisir l'adresse si la machine en a plusieurs.
  • L'envoi de données, dont TCP garantit qu'elles arriveront toutes et dans l'ordre.
  • La lecture de données. Rappelez-vous que TCP fournit un flot continu d'octets, deux envois de données feront peut-être une seule lecture ou bien un envoi deux lectures. C'est une des erreurs les plus courante des programmeurs débutants que de penser que s'ils font un write d'un côté, un read de l'autre lira tout ce que le write a écrit et seulement cela.
  • La fermeture propre (avec envoi, et réémission si nécessaire, des données écrites) de la connexion.
  • Et quelques autres services auxiliaires que je ne détaille pas ici (voir la section 3.9.1).

Rappelez-vous que l'API « socket » n'est qu'un des moyens pour l'application de parler à TCP. Et, à propos de cette API, notez que le RFC utilise le terme de socket avec un sens très différent (cf. le glossaire en section 4).

Et l'autre interface, « sous » TCP, l'interface avec la couche réseau (section 3.9.2) ? TCP peut fonctionner avec des couches réseau variées mais, en pratique, c'est toujours IP, dans une de ses deux versions, v4 ou v6. Un des points délicats est le traitement des messages ICMP que TCP peut recevoir. Au minimum, ils doivent être assignés à une connexion existante. Mais cela ne veut pas dire qu'il faut systématiquement agir lors de la réception d'un de ces messages (RFC 5461). Par exemple, il faut ignorer les messages ICMP de répression de la source (RFC 6633). Et il ne faut pas fermer la connexion juste parce qu'on a reçu une erreur ICMP (MUST-56), car elles peuvent être temporaires.

TCP étant un protocole orienté connexion, la connexion va avoir un état, représenté par un automate fini. Ainsi, l'ouverture d'une connexion par le premier TCP va le faire passer par les états CLOSED, SYN-SENT puis enfin ESTABLISHED. Récupérée sur Wikimedia Commons, voici une représentation de cet automate : TCP_state_diagram.jpg

La section 5 de notre RFC décrit les changements depuis le RFC 793. TCP reste le même et des TCP d'« avant » peuvent interopérer avec ceux qui suivent notre RFC récent. Les principaux changements sont :

  • La résolution d'un grand nombre d'errata accumulés depuis quarante ans.
  • La « démobilisation » du mécanisme de données urgentes (cf. RFC 6093).
  • L'incorporation d'une bonne partie des RFC comme le RFC 1011 ou le RFC 1122.

Le RFC 793 avait été écrit par Jon Postel (comme tant de RFC de cette époque), et notre nouveau RFC 9293 lui rend hommage, en estimant que la longue durée de vie du RFC 793 montre sa qualité.

En application des changements de ce nouveau RFC sur TCP, un registre IANA, celui des bits de l'en-tête comme URG (dont on a vu qu'il était désormais démobilisé), a été mis à jour.

La section 7 de notre nouveau RFC 9293 n'avait pas d'équivalent dans l'ancien RFC 793. Elle concerne la sécurité de TCP. TCP, par lui-même, fournit peu de sécurité (il protège quand même un peu contre l'usurpation d'adresse IP et l'injection de trafic, surtout contre un attaquant qui n'est pas sur le chemin, cf. RFC 5961). Par exemple, il ne fournit pas de services cryptographiques, donc pas de confidentialité ou de vraie garantie d'intégrité. (Son concurrent QUIC, lui, intègre systématiquement la cryptographie.) Si on veut davantage de sécurité pour TCP, il faut mettre IPsec en dessous de TCP ou bien TLS au-dessus. TCP a aussi des mécanismes de sécurité qui lui sont propres, comme AO, normalisé dans le RFC 5925 (mais très peu déployé en pratique). Autre expérience qui n'a pas été un succès, tcpcrypt (RFC 8548).

Il reste des attaques contre lesquelles la cryptographie ne fournit pas vraiment de solution. C'est le cas du SYN flooding, ou d'autres attaques par déni de service.

Mëme si on chiffre les données applicatives avec TLS, TCP expose quand même au réseau tout son fonctionnement. Cette vue depuis le réseau (RFC 8546) soulève plusieurs problèmes (elle peut par exemple faciliter des actions contraires à la neutralité du réseau) et c'est pour cela que le plus récent QUIC masque une grande partie de son fonctionnement (ce qui n'a pas été sans entrainer des polémiques). Toujours côté vie privée, le comportement des mises en œuvre de TCP est suffisamment différent pour qu'il soit possible dans certains cas d'identifier le système d'exploitation d'une machine à distance (ce n'est pas forcément grave, mais il faut le savoir).

Pour les programmeurs et programmeuses, l'annexe A du RFC contient différentes observations utiles pour la mise en œuvre concrète de TCP. Par exemple, cette annexe explique que la validation des numéros de séquence des segments TCP entrants, validation qui est nécessaire pour empêcher un grand nombre d'attaques, peut rejeter des segments légitimes. Ce problème n'a pas de solution idéale.

Un autre point intéressant concerne l'algorithme de Nagle. Cet algorithme représente un compromis entre la réactivité de l'application (qui nécessite d'envoyer les données le plus vite possibles) et l'optimisation de l'occupation du réseau (qui justifie d'attendre un peu pour voir si on ne va pas recevoir de nouvelles données à envoyer). Une modification de l'algorithme de Nagle, décrite dans le document draft-minshall-nagle peut rendre le choix moins douloureux.

Enfin, l'annexe B résume sous forme d'un tableau quelles sont les parties de la norme TCP qui doivent être mises en œuvre et quelles sont celles qu'on peut remettre à plus tard.

Si vous voulez sérieusement apprendre TCP, vous pouvez bien sûr lire le RFC en entier, mais il est sans doute plus sage de commencer par un bon livre comme le CNP3.

Un pcap d'une connexion TCP typique est utilisé ici pour illustrer le fonctionnement de TCP (fichier tcp-typique.pcap). Vu par tshark, cela donne :

8.445772 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 94 33672 → 443 [SYN] Seq=0 Win=64800 Len=0 MSS=1440 SACK_PERM=1 TSval=285818465 TSecr=0 WS=128
8.445856 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 94 443 → 33672 [SYN, ACK] Seq=0 Ack=1 Win=64260 Len=0 MSS=1440 SACK_PERM=1 TSval=3778761402 TSecr=285818465 WS=128
8.534636 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=1 Ack=1 Win=64896 Len=0 TSval=285818554 TSecr=3778761402
8.534897 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 SSLv3 236 Client Hello
8.534989 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 86 443 → 33672 [ACK] Seq=1 Ack=151 Win=64128 Len=0 TSval=3778761491 TSecr=285818554
8.548101 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TLSv1.2 1514 Server Hello
8.548111 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 1514 443 → 33672 [PSH, ACK] Seq=1429 Ack=151 Win=64128 Len=1428 TSval=3778761504 TSecr=285818554 [TCP segment of a reassembled PDU]
8.548265 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TLSv1.2 887 Certificate, Server Key Exchange, Server Hello Done
8.636922 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=151 Ack=2857 Win=63616 Len=0 TSval=285818656 TSecr=3778761504
8.636922 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=151 Ack=3658 Win=62848 Len=0 TSval=285818656 TSecr=3778761504
8.649899 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [FIN, ACK] Seq=151 Ack=3658 Win=64128 Len=0 TSval=285818669 TSecr=3778761504
8.650160 2605:4500:2:245b::42 → 2a01:4f8:c010:7eb7::106 TCP 86 443 → 33672 [FIN, ACK] Seq=3658 Ack=152 Win=64128 Len=0 TSval=3778761606 TSecr=285818669
8.739117 2a01:4f8:c010:7eb7::106 → 2605:4500:2:245b::42 TCP 86 33672 → 443 [ACK] Seq=152 Ack=3659 Win=64128 Len=0 TSval=285818758 TSecr=3778761606
  

Ici, 2a01:4f8:c010:7eb7::106 (une sonde RIPE Atlas) veut faire du HTTPS (donc, port 443) avec le serveur 2605:4500:2:245b::42. Tout commence par la triple poignée de mains (les trois premiers paquets, SYN, SYN+ACK, ACK). Une fois la connexion établie, tshark reconnait que les machines font du TLS mais, pour TCP, ce ne sont que des données applicatives (contrairement à QUIC, TCP sépare connexion et chiffrement). On note qu'il n'y a pas eu d'optimisation des accusés de réception. Par exemple, l'accusé de réception du ClientHello TLS voyage dans un paquet séparé, alors qu'il aurait pu être inclus dans la réponse applicative (le ServerHello), probablement parce que celle-ci a mis un peu trop de temps à être envoyée. À la fin de la connexion, chaque machine demande à terminer (FIN). Une analyse automatique plus complète, avec tshark -V figure dans tcp-typique.txt.

Ah, au fait, si vous avez pu lire cet article, c'est certainement grâce à TCP… (Ce blog ne fait pas encore de HTTP sur QUIC…).


Téléchargez le RFC 9293


L'article seul

RFC 9287: Greasing the QUIC bit

Date de publication du RFC : Août 2022
Auteur(s) du RFC : M. Thomson (Mozilla)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 30 août 2022


Dans un monde idéal, nos flux de données sur l'Internet passeraient sans problème et sans être maltraitées par les éléments intermédiaires du réseau. Mais dans le monde réel, de nombreuses middleboxes examinent le trafic et interfèrent avec lui. Les machines émettrices de trafic doivent donc se protéger et une des techniques les plus courantes est le chiffrement : si la middlebox ne peut pas comprendre ce qui est envoyé, elle ne pourra pas interférer. Le protocole de transport QUIC chiffre donc le plus possible. Mais une partie du trafic reste non chiffrée. Une des techniques pour empêcher que les middleboxes ne le lisent et ne prennent des décisions déplorables fondées sur ces informations transmises en clair est le graissage : on fait varier les données le plus possible. Ce RFC normalise une possibilité de graisser un bit de l'en-tête QUIC qui était fixe auparavant.

L'un des principes de conception de QUIC est de diminuer le plus possible la vue depuis le réseau (RFC 8546) ; les éléments intermédiaires, comme les routeurs ne doivent avoir accès qu'à ce qui leur est strictement nécessaire, le reste étant chiffré, pour leur éviter toute tentation. QUIC définit (dans le RFC 8999) des invariants, des informations qui seront toujours vraies même pour les futures versions de QUIC (le RFC 9000 définit QUIC version 1, pour l'instant la seule version). En dehors de ces invariants, tout peut… varier et les équipements intermédiaires du réseau ne doivent pas en tenir compte. Si tous suivaient ce principe, on préserverait la neutralité du réseau, et on éviterait l'ossification (cf. RFC 9170). Mais, en pratique, on sait que ce n'est pas le cas et que toute information visible depuis le réseau sera utilisée par les middleboxes, ce qui peut rendre difficile les évolutions futures (TLS a par exemple beaucoup souffert de ce problème, d'où le RFC 8701, le premier à avoir formalisé ce concept de graissage).

Or, QUIC a un bit qui n'est pas cité dans les invariants du RFC 8999 mais qui est fixe et peur servir à différencier QUIC d'autres protocoles. On l'appele d'ailleurs souvent le « bit QUIC » (même si ça n'est pas son nom officiel, qui est fixed bit, cf. RFC 9000, sections 17.2 et 17.3). Si les middleboxes s'en servent pour appliquer un traitement particulier à QUIC, les futures versions de QUIC ne pourront pas utiliser ce bit pour autre chose (rappelez-vous qu'il ne fait pas partie des invariants).

Notre RFC ajoute donc à QUIC une option pour graisser ce bit, c'est-à-dire pour le faire varier au hasard, décourageant ainsi les middleboxes d'en tenir compte. Un nouveau paramètre de transport QUIC (ces paramètres sont définis dans le RFC 9000, section 7.4) est créé, grease_quic_bit (et ajouté au registre IANA). Lorsqu'il est annoncé à l'ouverture de la connexion QUIC, il indique que ce bit est ignoré et que le pair à tout intérêt à le faire varier.

Les paquets initiaux de la connexion (Initial et Handshake) doivent garder le bit QUIC à 1 (puisqu'on n'a pas encore les paramètres de la connexion), sauf si on reprend une ancienne connexion (en envoyant un jeton, cf. RFC 9000, section 19.7).

Tout le but de ce graissage est de permettre aux futures versions de QUIC (v2, 3, etc) d'utiliser ce bit qui était originellement fixe. Ces futures versions, ou tout simplement des extensions à QUIC négociées au démarrage de la connexion, pourront donc utiliser ce bit (lui donner une sémantique). Le RFC leur recommande d'annoncer quand même le paramètre grease_quic_bit, ce qui permettra de graisser même si la future extension n'est pas gérée par le partenaire.

Le RFC note enfin que ce graissage rend plus difficile d'identifier les flux de données QUIC au milieu de tout le trafic. C'est bien sûr le but, mais cela peut compliquer certains activités d'administration réseau. Un futur RFC explique plus en détail cette situation.

Apparemment, plusieurs mises en œuvre de QUIC gèrent déjà ce graissage.


Téléchargez le RFC 9287


L'article seul

RFC 9285: The Base45 Data Encoding

Date de publication du RFC : Août 2022
Auteur(s) du RFC : P. Faltstrom (Netnod), F. Ljunggren (Kirei), D. van Gulik (Webweaving)
Pour information
Première rédaction de cet article le 10 août 2022


Ce RFC décrit un encodage en texte de données binaires, l'encodage Base45. Proche conceptuellement d'encodages comme Base64, il est, par exemple, beaucoup utilisé pour le code QR.

Ces codes QR ne permettent pas de stocker directement des données binaires quelconques car le contenu sera toujours interprété comme du texte. Il est donc nécessaire d'encoder et c'est le rôle de Base45. Il existe bien sûr d'innombrables autres mécanismes d'encodage en texte, comme Base16, Base32 et Base64, spécifiés dans le RFC 4648 mais Base45 est plus efficace pour les codes QR, qui réencodent ensuite.

Base45 utilise un sous-ensemble d'ASCII, de 45 caractères (d'où son nom). Deux octets du contenu binaire sont encodés en trois caractères de ce sous-ensemble. Par exemple, deux octets nuls donneront la chaine de caractères "000". Regardons tout de suite avec le module Python base45 :


% pip3 install base45
% python3             
>>> import base45
>>> base45.b45encode(b'Bonjour, tout le monde')
b'.H86/D34ENJES4434ESUETVDL44-3E6VC'
>>> 

  

Et décodons avec le module Elixir base45 :

    
% iex -S mix
iex(1)> Base45.decode(".H86/D34ENJES4434ESUETVDL44-3E6VC")
"Bonjour, tout le monde"

C'est parfait, tout le monde est d'accord. Ici, évidemment, la chaine originale n'avait pas vraiment besoin d'être encodée donc on va essayer avec du binaire, les trois octets 42, 1 et 6, encodage en Elixir, décodage en Python :

    
iex(1)> Base45.encode(<<42, 1, 6>>)
"/D560"

>>> base45.b45decode("/D560")
b'*\x01\x06'

  

Encore une fois, tout va bien, Elixir a encodé les trois octets du binaire en quatre caractères, que Python a su décoder (l'astérisque est affiché car son code ASCII est 42).

Je vous laisse découvrir l'algorithme complet (il est assez simple) dans la section 3 du RFC. Si vous le programmez vous-même (ce qui n'est sans doute pas une bonne idée, il existe déjà de nombreuses mises en œuvre), attention aux cas limites comme un binaire d'un seul octet, ou comme une chaine à décoder qui compte des caractères invalides. Ici, un essai avec un caractère invalide, le signe égal :


% python3
>>> import base45
>>> base45.b45decode("AAAA=")
Traceback (most recent call last):
  File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 30, in b45decode
    buf = [BASE45_DICT[c] for c in s.rstrip("\n")]
  File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 30, in <listcomp>
    buf = [BASE45_DICT[c] for c in s.rstrip("\n")]
KeyError: '='

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/stephane/.local/lib/python3.8/site-packages/base45/__init__.py", line 54, in b45decode
    raise ValueError("Invalid base45 string")
ValueError: Invalid base45 string

  

Pensez à tester votre code avec de nombreux cas variés. Le RFC cite l'exemple des chaines "FGW" (qui se décode en une paire d'octets valant chacun 255) et "GGW" qui, quoique proche de la précédente et ne comportant que des caractères de l'encodage Base45 est néanmoins invalide (testez-la avec votre décodeur). Le code de test dans le module Elixir donne des idées de tests utiles (certains viennent de bogues détectées pendant le développement). Regardez test/base45_test.exs.

Et, comme toujours, lorsque vous recevez des données venues de l'extérieur, soyez paranoïaques dans le décodage ! Un code QR peut être malveillant et chercher à activer une bogue dans le décodeur (section 6 du RFC).

Enfin, le RFC rappelle qu'une chaine encodée n'est pas forcément directement utilisable comme URL, elle peut comporter des caractères qui ne sont pas sûrs, il faut donc encore une étape d'encodage si on veut en faire un URL.

Compte tenu de l'importance des codes QR, on trouve des mises en œuvre de Base45 un peu partout. J'ai par exemple cité celle en Python. Si vous programmez en Elixir, il y a une bibliothèque sur Hex (écrite en Erlang) mais aussi ma bibliothèque (aux performances très basses).

Sinon, si vous cherchez un bon article d'explication sur Base45, je recommande celui-ci.


Téléchargez le RFC 9285


L'article seul

RFC 9267: Common Implementation Anti-Patterns Related to Domain Name System (DNS) Resource Record (RR) Processing

Date de publication du RFC : Juillet 2022
Auteur(s) du RFC : S. Dashevskyi, D. dos Santos, J. Wetzels, A. Amri (Forescout Technologies)
Pour information
Première rédaction de cet article le 14 septembre 2022


Comme chacun·e sait, l'Internet est une jungle (les politiciens ajouteraient « une jungle Far-West de non-droit qu'il faut civiliser »). Par exemple, les logiciels qui parlent avec les vôtres ne sont pas toujours correctement écrits, voire ils sont franchement malveillants. Le code de votre côté doit donc être robuste, voire paranoïaque, et penser à tout. Ce RFC décrit quelques problèmes qui ont été observés dans des logiciels mettant en œuvre le DNS et explique comment ne pas refaire la même erreur.

Le RFC ne parle pas de failles DNS mais de failles dans les programmes DNS, ce qui est très différent (mais la différence est souvent oubliée dans les médias). Le protocole lui-même n'était pas en cause dans ces cas, ce sont juste les logiciels qui avaient des bogues. Les cas sont nombreux, par exemple SIGRed (CVE-2020-1350) ou DNSpooq (CVE-2020-25681 à CVE-2020-25687). Ces problèmes frappent notamment souvent dnsmasq (personnellement, je n'ai jamais compris pourquoi ce logiciel était si utilisé, mais c'est une autre histoire).

Plusieurs vulnérabilités ont concerné l'analyse des enregistrements DNS (RR = Resource Record). Les risques lors de cette analyse devraient être bien connus, la première faille documentée l'ayant été en 2000 (CVE-2000-0333) ! Tout logiciel qui analyse des enregistrements DNS (client, serveurs, mais aussi pare-feux, IDS, etc) peut tomber dans ces pièges, d'où l'importance de les documenter. C'était déjà fait dans l'Internet-Draft draft-ietf-dnsind-local-compression et dans le RFC 5625 mais c'était perdu au milieu d'autres choses donc notre RFC choisit d'enfoncer le clou.

Il commence par le grand classique des bogues de logiciels DNS : le traitement des pointeurs de compression. Pour gagner quelques octets, à l'époque où ça comptait, le DNS prévoit (RFC 1035, section 4.1.4) qu'on peut, dans un enregistrement DNS, remplacer tout ou partie d'un nom de domaine par un pointeur vers un autre endroit du paquet. Ainsi, si on a tous ses serveurs qui se terminent par les mêmes composants (comme c'est le cas, par exemple, de la racine), on peut ne mettre ces composants qu'une fois, et pointer vers eux partout ailleurs. Évidemment, si vous êtes programmeuse ou programmeur, vous avez déjà vu le piège : les pointeurs, c'est dangereux. Ils peuvent pointer en dehors du paquet, ou pointer vers eux-même, par exemple. Si on suit aveuglément un pointeur, un déni de service est possible, si on tape en dehors de la mémoire allouée ou bien si on se lance dans une boucle sans fin.

Rentrons dans les détails. Le RFC 1035 nous dit que l'octet qui indique la longueur d'un composant doit valoir moins de 64 (la taille maximale d'un composant), donc avoir les deux bits de plus fort poids à zéro. S'ils sont tous les deux à un, cela indique qu'on a un pointeur. Cet octet et le suivant (privés des deux bits de plus fort poids) sont alors un pointeur, le nombre d'octets depuis le début du message. On voit qu'on peut atteindre 16 383 octets (2 ** 14 - 1), largement assez pour sortir de la mémoire allouée pour le paquet, avec les conséquences qu'on imagine. Cela s'est produit en vrai (CVE-2020-25767, CVE-2020-24339 et CVE-2020-24335).

Autre exemple amusant cité par le RFC, le cas d'un pointeur pointant sur lui-même. Soit un message DNS minimal. Le premier composant est à 12 octets du début du message. Si on y met les octets 0xC0 et 0x0C, on aura un pointeur (0xC0 = 11000000, les deux bits de plus fort poids à un) qui vaut 12 (0xC0 0x0C moins les deux bits les plus significatifs = 0000000000001100 = 12). Le pointeur pointera alors sur lui-même, entrainant le logiciel DNS imprudent dans une boucle sans fin. Ça aussi, ça s'est déjà produit (CVE-2017-9345).

Dernier exemple amusant avec des pointeurs, le pointeur qui va mener à un nombre infini de composants. L'attaquant (ou le programmeur maladroit) met dans le message un composant suivi d'un pointeur qui revient au début de ce composant. Si le composant était test, un analyseur DNS imprudent va créer le nom de domaine test.test.test.test… avant de tomber à court de mémoire, ou bien, dans un langage de programmation comme C, d'écrire dans une autre zone de la mémoire, ce qui peut mener à un RCE. Notez que les pointeurs ne devraient pas pointer vers un pointeur : ça n'a aucun intérêt pratique et c'est dangereux, mais le RFC 1035 ne l'interdit pas explicitement. Une alternative est de tester le nombre de fois qu'on a suivi un pointeur ou, encore mieux, de vérifier que le pointeur ne pointe qu'en avant de lui-même.

Notez que le problème de la répétition infinie d'un composant pourrait également être évité en s'assurant que le nom de domaine reste en dessous de sa taille maximale, 255 octets (section 3 du RFC).

Mais il n'y a pas que les pointeurs. Un nom de domaine doit être terminé par un octet nul, indiquant la racine. Ainsi, le nom afnic.fr est encodé 0x05 0x61 0x66 0x6E 0x69 0x63 ("afnic") 0x02 0x66 0x72 ("fr") 0x00 (la racine). Que se passe-t-il si l'octet nul final manque ? Certains programmes en C ont été assez imprudents pour tenter de traiter le nom de domaine avec des routines comme strlen, qui comptent sur un octet nul pour terminer les données (section 4 de notre RFC). Le développeur qui ne veut pas refaire des failles comme CVE-2020-25107, CVE-2020-17440, CVE-2020-24383 ou CVE-2020-27736 devrait utiliser strnlen ou une autre méthode sûre.

On n'a pas terminé. Lorsqu'on traite une réponse DNS, les enregistrements incluent des données. Leur longueur est donné par un champ RDLENGTH de deux octets, qui précède les données (section 5). Si un analyseur DNS ne fait pas attention, la longueur indiquée peut être supérieure à la taille du paquet. Un programme qui, bêtement, lirait RDLENGTH puis ferait un read() de la longueur indiquée aurait de fortes chances de taper en dehors de la mémoire accessible (CVE-2020-25108, CVE-2020-24336 et CVE-2020-27009).

Ah, et le RFC n'en parle pas, mais l'analyse des options EDNS (RFC 6891) offre également ce genre de pièges. Une option est encodée en TLV et une longueur invalide peut mener à lire les valeurs en dehors du paquet.

Un problème du même genre est décrit dans la section 6 du RFC. Un message DNS comprend plusieurs sections (Question, Réponse, Autorité, Additionnelle) et le nombre d'enregistrements par section est indiqué au début du message DNS. Ces nombres (count) peuvent également être faux et ne doivent pas être utilisés aveuglément, sinon, on aura des malheurs du genre CVE-2020-25109, CVE-2020-24340, CVE-2020-24334 ou CVE-2020-27737.

C'est en raison de tous ces risques qu'il faut tester que ses programmes résistent bien à des messages DNS mal formés. Par exemple, le serveur faisant autorité Drink, écrit en Elixir, a dans ses jeux de tests de tels messages. Regardez par exemple drink_parsing_test.exs. Un exemple d'un tel test, pour le problème de boucle sans fin sur des pointeurs, décrit en section 2 du RFC, est :

    
  test "pointerloop1" do # RFC 9267, section 2
    result = Drink.Parsing.parse_request(<<@id::unsigned-integer-size(16),
      @request::unsigned-integer-size(16),
      1::unsigned-integer-size(16), # QDcount
      0::unsigned-integer-size(16), # ANcount
      0::unsigned-integer-size(16), # NScount
      0::unsigned-integer-size(16), # ARcount
      0xc0::unsigned-integer-size(8), # First label of the question
				      # section starts with a
				      # compression pointer to itself.
      0x0c::unsigned-integer-size(8),
      @mx::unsigned-integer-size(16),
      @in_class::unsigned-integer-size(16)
      >>, 
      false)
    assert result == {:error, :badDNS}    
  end

  

On fabrique un paquet DNS avec le pointeur invalide, et on l'analyse. Le sous-programme parse_request ne doit pas tourner sans fin, et doit renvoyer une erreur.

Un test du même genre en Python, où on envoie un message DNS avec un pointeur pointant sur lui-même (et on espère que ça n'aura pas tué le serveur qui le reçoit) :

id = 12345
misc = 0 # opcode 0, all flags zero 
data = struct.pack(">HHHHHHBB", id, misc, 1, 0, 0, 0, 0xc0, 0x0c)
s.sendto(data, sockaddr)
  

Téléchargez le RFC 9267


L'article seul

RFC 9260: Stream Control Transmission Protocol

Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Stewart (Netflix), M. Tüxen (Münster Univ. of Appl. Sciences), K. Nielsen (Kamstrup A/S)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF twvwg
Première rédaction de cet article le 6 juin 2022


Une des particularités du protocole IP est que vous avez plusieurs protocoles de transport disponibles. TCP et UDP sont les plus connus mais SCTP, normalisé dans notre RFC, est également intéressant. C'est un protocole déjà ancien (il date de 2000) et ce RFC remplace son prédécesseur, le RFC 4960. Beaucoup de changements de détails, mais rien de crucial.

SCTP ressemble plutôt à TCP, notamment par le fait qu'il fournit un transport fiable. Mais il a plusieurs différences :

  • il ne gère pas un flot d'octets continu mais une série de messages, bien séparés,
  • il gère plusieurs flux de données séparés, qui partagent le même contrôle de congestion mais gèrent à part les pertes et retransmissions (un peu comme QUIC),
  • il gère le cas où la machine a plusieurs adresses IP, ce qui lui fournit normalement plus de redondance, si on est connecté à plusieurs réseaux.

Cette dernière possibilité le rapproche des protocoles qui séparent l'identificateur et le localisateur et lui permet de gérer proprement le multihoming. Cela se fait en indiquant, au début du contact, toutes les adresses IP (autrefois, il y avait même les noms de domaines, cf. la section 3.3.2.1.4) de la machine.

SCTP tient également compte de l'expérience acquise avec TCP. par exemple, l'établissement d'une connexion (que SCTP nomme association) se fait avec un échange de quatre paquets (et non pas trois comme avec TCP), pour offrir une meilleure protection contre les dénis de service. Les SYN cookies, un ajout plus ou moins bancal en TCP, sont ici partie intégrante du protocole.

SCTP est surtout issu des demandes du monde de la téléphonie sur IP, qui avait besoin d'un tel protocole pour la signalisation mais il peut être aussi utilisé dans d'autres domaines.

Un excellent article du Linux Journal explique bien SCTP.

SCTP est depuis longtemps mis en œuvre dans Linux et dans FreeBSD. De même, des programmes de débogage comme Wireshark sont capables de décoder et d'afficher le SCTP. Voici par exemple un pcap entre deux machines dont l'une a envoyé (trame 3) la chaîne de caractères « toto ». (Vous avez également la version texte de ce dialogue.) Des exemples de programmes de tests SCTP se trouvent dans mon article sur le RFC 3286.

Comme tout « nouveau » protocole de transport, SCTP est handicapé par des coupe-feux mal programmés et/ou mal configurés. L'Internet s'ossifiant de plus en plus, il devient très difficile de déployer un nouveau protocole de transport. D'où le RFC 6951, pour faire tourner SCTP sur UDP (comme ce que fait QUIC).

Les changements qu'introduit ce nouveau RFC ne modifient pas en profondeur le protocole mais corrigent les nombreux problèmes survenus pendant ses premières années d'utilisation. Le RFC 8540 donne la liste complète des problèmes corrigés.


Téléchargez le RFC 9260


L'article seul

RFC 9255: The 'I' in RPKI Does Not Stand for Identity

Date de publication du RFC : Juin 2022
Auteur(s) du RFC : R. Bush (Arrcus & IIJ Research), R. Housley (Vigil Security)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF sidrops
Première rédaction de cet article le 15 juin 2022


Un très court RFC pour un simple rappel, qui ne devrait même pas être nécessaire : les identités utilisées dans la RPKI, la base des identités qui sert aux techniques de sécurisation de BGP, n'ont pas de lien avec les identités attribuées par l'État et ne doivent pas être utilisées pour, par exemple, signer des documents « officiels » ou des contrats commerciaux.

Le RFC 6480 décrit cette RPKI : il s'agit d'un ensemble de certificats et de signatures qui servent de base pour des techniques de sécurité du routage comme les ROA du RFC 6482 ou comme le BGPsec du RFC 8205. Les objets de la RPKI servent à établir l'autorité sur des ressources Internet (INR, Internet Number Resources, comme les adresses IP et les numéros d'AS). Le RFC 6480, section 2.1, dit clairement que cela ne va pas au-delà et que la RPKI ne prétend pas être la source d'identité de l'Internet, ni même une source « officielle ».

Prenons un exemple concret, un certificat choisi au hasard dans les données du RIPE-NCC (qu'on peut récupérer avec rsync en rsync://rpki.ripe.net/repository). Il est au format DER donc ouvrons-le :

%  openssl x509 -inform DER -text -in  ./DEFAULT/ztLYANxsM7afpHKR4vFbM16jYA8.cer
Certificate:
    Data:
        ...    
        Serial Number: 724816122963 (0xa8c2685453)
        Validity
            Not Before: Jan  1 14:04:04 2022 GMT
            Not After : Jul  1 00:00:00 2023 GMT
        Subject: CN = ced2d800dc6c33b69fa47291e2f15b335ea3600f
	...
        X509v3 extensions:
            ...
            sbgp-ipAddrBlock: critical
                IPv4:
                  51.33.0.0/16
                  ...
            sbgp-autonomousSysNum: critical
                Autonomous System Numbers:
                  206918
                  ...
  

Je n'ai pas tout montré, mais seulement les choses importantes :

  • Il n'y a pas de vraie identité dedans, le sujet a manifestement été généré automatiquement,
  • Il couvre un certain nombre de ressources (INR, Internet Number Resources), comme le préfixe IP 51.33.0.0/16, utilisant les extensions du RFC 3779.

Ce certificat dit simplement que l'entité qui a la clé privée (RFC 5280) correspondante (une administration britannique, dans ce cas) a autorité sur des ressources comme l'AS 206918. Rien d'autre.

Mais, apparemment, certaines personnes n'avaient pas bien lu le RFC 6480 et croyaient que cet attirail PKIesque leur permettait également de signer des documents sans lien avec les buts de la RPKI, voire n'ayant rien à voir avec le routage. Et d'autant plus que des gens croient que le I dans RPKI veut dire Identity (il veut en fait dire Infrastructure). Il a ainsi été suggéré que les clés de la RPKI pouvaient être utilisées pour signer des LOA demandant l'installation d'un serveur dans une baie.

Pourquoi est-ce que cela serait une mauvaise idée ? Il y a plusieurs raisons mais la principale est qu'utiliser la RPKI pour des usages en dehors du monde restreint du routage Internet est que cela exposerait les AC de cette RPKI à des risques juridiques qu'elles n'ont aucune envie d'assumer. Et cela compliquerait les choses, obligeant sans doute ces AC à déployer des processus bureaucratiques bien plus rigides. Si on veut connaitre l'identité officielle (que le RFC nomme à tort « identité dans le monde réel ») d'un titulaire de ressources Internet, on a les bases des RIR, qu'on interroge via RDAP ou autres protocoles. (C'est ainsi que j'ai trouvé le titulaire de 51.33.0.0/16.) Bien sûr, il y a les enregistrements Ghostbusters du RFC 6493 mais ils sont uniquement là pour aider à trouver un contact opérationnel, pas pour indiquer l'identité étatique du titulaire. Même les numéros d'AS ne sont pas l'« identité » d'un acteur de l'Internet (certains en ont plusieurs).

Notons qu'il y a d'autres problèmes avec l'idée de se servir de la RPKI pour signer des documents à valeur légale. Par exemple, dans beaucoup de grandes organisations, ce ne sont pas les mêmes personnes qui gèrent le routage et qui gèrent les commandes aux fournisseurs. Et, au RIPE-NCC, les clés privées sont souvent hébergées par le RIPE-NCC, pas par les titulaires, et le RIPE-NCC n'a évidemment pas le droit de s'en servir pour autre chose que le routage.

Bref, n'utilisez pas la RPKI pour autre chose que ce pour quoi elle a été conçue.


Téléchargez le RFC 9255


L'article seul

RFC 9250: DNS over Dedicated QUIC Connections

Date de publication du RFC : Mai 2022
Auteur(s) du RFC : C. Huitema (Private Octopus), S. Dickinson (Sinodun IT), A. Mankin (Salesforce)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 22 mai 2022


Ce nouveau RFC complète la série des mécanismes de protection cryptographique du DNS. Après DoT (RFC 7858) et DoH (RFC 8484), voici DoQ, DNS sur QUIC. On notera que bien que QUIC ait été conçu essentiellement pour les besoins de HTTP, c'est le DNS qui a été la première application normalisée de QUIC.

Fonctionnellement, DoQ est très proche de ses prédécesseurs, et offre les mêmes services de sécurité, confidentialité, grâce au chiffrement, et authentification du serveur, via le certificat de celui-ci. L'utilisation du transport QUIC permet quelques améliorations, notamment dans le « vrai » parallélisme. Contrairement à DoT (RFC 7858) et DoH (RFC 8484), DoQ peut être utilisé pour parler à un serveur faisant autorité aussi bien qu'à un résolveur.

Notez que le terme de DoQ (DNS over QUIC) n'apparait pas dans le RFC de terminologie DNS, le RFC 8499. Il sera normalement dans son successeur.

Logiquement, DoQ devrait faire face aux mêmes problèmes politiques que ceux vécus par DoH (voir aussi mon article dans Terminal) mais cela n'a pas été encore le cas. Il faut dire qu'il y a peu de déploiements (ne comptez pas installer DoQ comme serveur ou client tout de suite, le code est loin d'être disponible partout).

Le cahier des charges de DoQ est (sections 1 et 3 du RFC) :

  • Protection contre les attaques décrites dans le RFC 9076.
  • Même niveau de protection que DoT (RFC 7858, avec notamment les capacités d'authentification variées du RFC 8310). Rappelez-vous que QUIC est toujours chiffré (actuellement avec TLS, cf. RFC 9001).
  • Meilleure validation de l'adresse IP source qu'avec le classique DNS sur UDP, grâce à QUIC.
  • Pas de limite de taille des réponses, quelle que soit la MTU du chemin.
  • Diminution de la latence, toujours grâce à QUIC et notamment ses possibilités de connexion 0-RTT (RFC 9000, section 5), ses procédures de récupération en cas de perte de paquets (RFC 9002) et la réduction du head-of-line blocking, chaque couple requête/réponse étant dans son propre ruisseau.

En revanche, DoQ n'esssaie pas de :

  • Contourner le blocage de QUIC que font certaines middleboxes (DoH est meilleur, de ce point de vue). La section 3.3 du RFC détaille cette limitation de DoQ : il peut être filtré trop facilement, son trafic se distinguant nettement d'autres trafics.
  • Ou de permettre des messages du serveur non sollicités (pour cela, il faut utiliser le RFC 8490).

DoQ utilise QUIC. QUIC est un protocole de transport généraliste, un concurrent de TCP. Plusieurs protocoles applicatifs peuvent utiliser QUIC et, pour chacun de ces protocoles, cela implique de définir comment sont utilisés les ruisseaux (streams) de QUIC. Pour HTTP/3, cela a été fait dans le RFC 9113, publié plus tard. Ironiquement, le DNS est donc le premier protocole à être normalisé pour une utilisation sur QUIC. Notez qu'une autre façon de faire du DNS sur QUIC est de passer par DoH et HTTP/3 (c'est parfois appelé DoH3, terme trompeur) mais cette façon n'est pas couverte dans notre RFC, qui se concentre sur DoQ, du DNS directement sur QUIC, sans HTTP.

La spécification de DoQ se trouve en section 4. Elle est plutôt simple, DoQ étant une application assez directe de QUIC. Comme DoQ utilise QUIC, il y a forcément un ALPN, en l'occurrence via l'identificateur doq.

Le port, quant à lui, est par défaut 853, également nommé domain-s. C'est donc le même que DoT, et aussi le même que le DNS sur DTLS (RFC 8094, protocole expérimental, jamais vraiment mis en œuvre et encore moins déployé, et qui est de toute façon distinguable du trafic QUIC, cf. section 17.2 du RFC 9000). Bien sûr, un client et un serveur DNS majeurs et vaccinés peuvent toujours se mettre d'accord sur un autre port (mais pas 53, réservé au DNS classique). Cette utilisation d'un port spécifique à DoQ est un des points qui le rend vulnérable au filtrage, comme vu plus haut. Utiliser 443 peut aider. (Le point a été chaudement discuté à l'IETF, entre défenseurs de la vie privée et gestionnaires de réseau qui voulaient pouvoir filtrer facilement les protocoles de leur choix. Si on utilise le port 443, il faut se rappeler que l'ALPN est pour l'instant en clair, la lutte de l'épée et de la cuirasse va donc continuer.)

Plus important, la question de la correspondance entre les messages DNS et les ruisseaux QUIC. La règle est simple : pour chaque requête DNS, un ruisseau est créé. La première requête sur une connexion donnée sera donc sur le ruisseau 0 (RFC 9000, section 2.1), la deuxième sur le ruisseau 4, etc. Si les connexions QUIC sont potentiellement de longue durée, en revanche, les ruisseaux ne servent qu'une fois. Le démultiplexage des réponses est assuré par QUIC, pas par le DNS (et l'identificateur de requête DNS est donc obligatoirement à zéro, puisqu'inutile). Le parallélisme est également fourni par QUIC, client et serveur n'ont rien de particulier à faire. Il est donc recommandé de ne pas attendre une réponse pour envoyer les autres questions. Les messages DNS sont précédés de deux octets indiquant leur longueur, comme avec TCP.

Comme avec TCP, client et serveur ont le droit de garder un œil sur les ruisseaux et les connexions inactifs, et de les fermer d'autorité. La section 5.5 donne des détails sur cette gestion de connexion, mais le RFC 7766 est également une bonne lecture (voir aussi le RFC 9103 pour le cas des transferts de zones).

DoQ introduit quelques codes d'erreur à lui (RFC 9000, section 20.2), comme DOQ_INTERNAL_ERROR (quelque chose s'est mal passé), DOQ_PROTOCOL_ERROR (quelqu'un n'a pas lu le RFC, par exemple l'identificateur de requête était différent de zéro) ou DOQ_EXCESSIVE_LOAD (trop de travail tue le travail).

Un des gros avantages de QUIC est le 0-RTT, où des données sont envoyées dès le premier paquet, ce qui réduit nettement la latence. Cela implique que le client ait déjà contacté le serveur avant, mémorisant les informations qui seront données au serveur pour qu'il retrouve la session à reprendre. C'est cool, mais cela pose d'évidents problèmes de vie privée (ces informations mémorisées sont une sorte de cookie, et permettent le suivi à la trace d'un client). D'ailleurs, en parlant de vie privée, le RFC signale aussi (section 5.5.4) que la possibilité de migrer une connexion QUIC d'une adresse IP à l'autre a également des conséquences pour la traçabilité.

Autre piège avec le 0-RTT, il ne protège pas contre le rejeu et il ne faut donc l'utiliser que pour des requêtes DNS idempotentes comme QUERY (le cas le plus courant) ou NOTIFY (qui change l'état du serveur, mais est idempotent). Bon, de toute façon, un serveur peut toujours être configuré pour ne pas accepter de 0-RTT, et répondre alors REFUSED (avec un code d'erreur étendu - RFC 8914 - Too Early).

La section 5 du RFC liste les exigences auxquelles doivent se soumettre les mises en œuvre de DoQ. Le client doit authentifier le serveur (cf. RFC 7858 et RFC 8310, mais aussi RFC 8932, sur les bonnes pratiques). Comme tous les serveurs ne géreront pas DoQ, en tout cas dans un avenir prévisible, les clients doivent être préparés à ce que ça échoue et à réessayer, par exemple en DoT.

QUIC ne dissimule pas forcément la taille des messages échangés, et celle-ci peut être révélatrice. Malgré le chiffrement, la taille d'une requête ou surtout d'une réponse peut donner une idée sur le nom demandé. Le RFC impose donc l'utilisation du remplissage, soit par la méthode QUIC générique de la section 19.1 du RFC 9000, soit par la méthode spécifique au DNS du RFC 7830. La première méthode est en théorie meilleure car elle dissimule d'autres métadonnées, et qu'elle tient compte de davantage d'éléments, mais les bibliothèques QUIC n'exposent pas forcément dans leur API de moyen de contrôler ce remplissage. Le RFC recommande donc aux clients et serveurs DoQ de se préparer à faire le remplissage eux-mêmes (en relisant bien le RFC 8467 avant).

Un petit retour sur la protection de la vie privée en section 7. Cette section rappele l'importance de lire et suivre le RFC 8932 si vous gérez un service de résolution DNS sécurisé (DoQ ou pas DoQ). Et elle insiste sur le fait que des fonctions de QUIC très pratiques pour diminuer la latence à l'établissement de la connexion, notamment le 0-RTT, ont des conséquences pour la vie privée, en permettant de suivre un même utilisateur (plus exactement une même machine). Et cela marche même si la machine a changé d'adresse IP entretemps. On peut aussi dire que le problème de QUIC est de permettre des sessions de très longue durée (que ce soit une vraie session unique, ou bien une « session virtuelle », formée en reprenant une session existante avec le 0-RTT) et que de telles sessions longues, excellentes pour les performances, le sont forcément moins pour l'intimité.

Les mises en œuvre de DoQ, maintenant. La société AdGuard a produit du code, dont un serveur et un client en Go, dnslookup. (Regardez l'exposé du directeur technique de cette société à l'OARC.) Voici un exemple de compilation, puis d'utilisation de dnslookup :


% git clone https://github.com/ameshkov/dnslookup.git
%  cd dnslookup
% make
% ./dnslookup www.bortzmeyer.org quic://dns.adguard.com

On utilisait le serveur DoQ public d'AdGuard. Officiellement, NextDNS en a également un mais je n'arrive pas à l'utiliser :

% ./dnslookup www.bortzmeyer.org quic://3e4935.dns.nextdns.io
...
2022/05/22 08:16:33 Cannot make the DNS request: opening quic session to quic://3e4935.dns.nextdns.io:853: timeout: no recent network activity
  

Notez que si vous voulez utiliser dnslookup avec un serveur dont le certificat n'est pas valide, il faut mettre la variable d'environnement VERIFY à 0 (cela ne semble pas documenté).

Il existe une autre mise en œuvre de DoQ, quicdoq, écrite en C. Elle dépend de la bibliothèque QUIC picoquic qui, à son tour, dépend de picotls, dont la compilation n'est pas toujours évidente. D'une manière générale, les bibliothèques QUIC sont souvent récentes, parfois expérimentales, et ne marchent pas toujours du premier coup. Mais si vous arrivez à compiler les dépendances de quicdoq, vous pouvez lancer le serveur ainsi :

% ./quicdoq_app -p 8053 -d 9.9.9.9
  

Puis le client :

% ./quicdoq_app ::1 8053 foobar:SOA bv:NS www.bortzmeyer.org:AAAA
  

Le logiciel de test de performances Flamethrower, écrit en C++ a une branche DoQ en cours de développement.

Le logiciel en Python aioquic ne semble pouvoir interagir qu'avec lui-même. Vu les messages d'erreur (quelque chose à propos de not enough bytes), je soupçonne qu'il utilise l'encodage des débuts de DoQ, quand il n'y avait pas encore le champ de deux octets au début des messages. Même avec lui-même, il a des exigences pénibles en matière de certificat (pas de certificats auto-signés, obligation que le nom du serveur soit dans le SAN - Subject Alternative Name, pas seulement dans le sujet).

Pour PowerDNS, il n'y a pour l'instant qu'un ticket. Et pour Unbound, c'est en cours, ainsi que pour Knot.

Dans les bibliothèques DNS, un travail est en cours pour go-dns.

Pour finir, vous pouvez regarder la présentation de DoQ par une des auteures du RFC au RIPE 84. Et un des premiers articles de recherche sur DoQ est « One to Rule them All? A First Look at DNS over QUIC ».


Téléchargez le RFC 9250


L'article seul

RFC 9239: Updates to ECMAScript Media Types

Date de publication du RFC : Mai 2022
Auteur(s) du RFC : M. Miller, M. Borins (GitHub), M. Bynens (Google), B. Farias
Pour information
Réalisé dans le cadre du groupe de travail IETF dispatch
Première rédaction de cet article le 11 mai 2022


Ce RFC documente les types de médias pour le langage de programmation JavaScript (dont le nom officiel est ECMAScript, qu'on retrouve dans le titre de ce RFC). Il remplace le RFC 4329 et le principal changement est que le type recommandé est désormais text/javascript et non plus application/javascript.

Si vous voulez vous renseigner en détail sur JavaScript, notre RFC recommande de lire la norme ECMA, en https://262.ecma-international.org/12.0/. Cette norme est développée en dehors de l'IETF et le choix des types de médias (aussi appelés types MIME, cf. RFC 2046) n'est donc pas forcément en accord avec les règles de l'IETF (RFC 6838). C'est pour cela que l'IESG a ajouté une note d'avertissement au RFC. Mais, bon, ce n'est pas trop grave en pratique. Le type recommandé est donc désormais text/javascript. D'autres types existent, application/ecmascript, application/javascript, etc, mais ils sont maintenant considérés comme dépassés.

Il existe plusieurs versions de la norme JavaScript et d'autres apparaitront peut-être dans le futur. Mais le type officiel n'indique pas de version (il a existé des propositions comme text/javascript1.4) et compte que toutes ces versions sont suffisamment compatibles pour qu'on ne gère pas la complexité d'avoir plusieurs types. Normalement, ECMA s'engage à ne pas bouleverser le langage.

Le choix de text/ est contestable car la définition originale de text/ dans le RFC 2045 prévoyait plutôt application/ pour les programmes. Le RFC 4329 enregistrait les types text/javascript et application/javascript, et recommandait le application/javascript (en accord avec ce que dit le RFC 6838). Aujourd'hui, c'est le contraire, text/javascript est le préféré. D'une manière générale, application/ n'a guère été utilisé (pas seulement dans le cas de JavaScript). (Personnellement, cela me convient très bien : pour moi, si ça peut s'afficher avec cat et s'éditer avec vi, c'est du texte.)

JavaScript est donc officiellement du texte, et la section 4 de notre RFC précise : du texte en UTF-8. Le paramètre charset est donc facultatif (même si le RFC 6838 dit le contraire). Si vous aimez les détails d'encodage, la section 4 vous ravira (c'est l'un des points qui a suscité le plus de discussion à l'IETF).

Le type text/javascript est enregistré à l'IANA ; les types comme application/javascript sont marqués comme dépassés. Même chose pour text/ecmascript. Le nom officiel de JavaScript est ECMAscript, puisque normalisé à l'ECMA mais personne n'utilise ce terme. (Il faut quand même noter que JavaScript est un terme publicitaire mensonger puisqu'il avait été choisi par le marketing pour profiter de la popularité - à l'époque - de Java, un langage avec lequel il n'a rien à voir.) Enfin, les types comme text/x-javascript qu'on voit parfois trainer datent d'avant le RFC 6648, qui abandonnait les identificateurs semi-privés commençant par x-.

La section 5 couvre les questions de sécurité. Elle est très longue car l'envoi de JavaScript à un programme qui l'exécutera (ce qui est très courant sur le Web) pose plein de problèmes de sécurité. Le RFC rappelle que l'exécution automatique d'un programme fourni par un tiers est évidemment intrinsèquement dangereuse, et doit se faire dans un bac à sable. Parmi les dangers (mais il y en a beaucoup d'autres !) le déni de service puisque JavaScript est un langage de Turing et permet donc, par exemple, des boucles infinies.

L'annexe B du RFC résume les changements depuis le RFC 4329 : le principal est bien sûr l'abandon de application/javascript au profit de text/javascript. Il y a aussi l'ajout de quelques détails comme une faille de sécurité nouvelle, et le cas des modules JavaScript.

Ah, un point de détail cité au détour d'un paragraphe du RFC : il n'y a pas de norme pour les identificateurs de fragments dans un URI pointant vers une ressource de type text/javascript. Si https://code.example/js/foreach.js#l23 pointe vers du JavaScript, la signification du #l23 (l'identificateur de fragment) n'est pas spécifiée.


Téléchargez le RFC 9239


L'article seul

RFC 9234: Route Leak Prevention and Detection Using Roles in UPDATE and OPEN Messages

Date de publication du RFC : Mai 2022
Auteur(s) du RFC : A. Azimov (Qrator Labs & Yandex), E. Bogomazov (Qrator Labs), R. Bush (IIJ & Arrcus), K. Patel (Arrcus), K. Sriram (USA NIST)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF idr
Première rédaction de cet article le 25 mai 2022


Vous savez sans doute que l'Internet repose sur le protocole de routage BGP et que ce protocole a été cité dans des accidents (voire des attaques) spectaculaires, par exemple lorsqu'un routeur BGP annonçait ou réannonçait des routes qu'il n'aurait pas dû annoncer (ce qu'on nomme une fuite de routes). Si BGP lui-même est pair-à-pair, en revanche, les relations entre les pairs ne sont pas forcément symétriques et des règles sont largement admises ; par exemple, on n'est pas censé annoncer à un transitaire des routes apprises d'un autre transitaire. Ce RFC étend BGP pour indiquer à l'ouverture de la session le type de relations qu'on a avec son pair BGP, ce qui permet de détecter plus facilement certaines fuites de routes.

La solution proposée s'appuie sur un modèle des relations entre opérateurs, le modèle « sans vallée » (valley-free, même si ce terme n'est pas utilisé dans le RFC). On le nomme aussi modèle Gao-Rexford. Ce modèle structure ces relations de telle façon à ce que le trafic aille forcément vers un opérateur aussi ou plus important (la « montagne »), avant de « redescendre » vers la destination ; le trafic ne descend pas dans une « vallée », sauf si elle est la destination finale. Le but est de permettre un routage optimum et d'éviter boucles et goulets d'étranglement, mais ce modèle a aussi une conséquence politico-économique, maintenir la domination des gros acteurs (typiquement les Tier-1). Les relations entre participants à une session BGP sont de type Client-Fournisseur ou Pair-Pair, et le trafic va toujours dans le sens du client vers le fournisseur, sauf pour les destinations finales (on n'utilise donc pas un pair pour du transit, c'est-à-dire pour joindre d'autres réseaux que ceux du pair).

En raison de ce modèle, un routeur BGP n'est pas censé annoncer à un transitaire des routes apprises d'un autre transitaire, ou annoncer à un pair des routes apprises d'un transitaire, ou bien apprises d'un autre pair (RFC 7908). Une fuite de routes est une annonce qui viole cette politique (qu'elle soit explicite ou implicite).

Traditionnellement, on empêche ces fuites par des règles dans la configuration du routeur. Par exemple, le client d'un transitaire va bloquer l'exportation par défaut, puis lister explicitement les routes qu'il veut annoncer. Et le transitaire va bloquer l'importation par défaut, et lister ensuite les routes qu'il acceptera de son client. Ce nouveau RFC ajoute un autre mécanisme, où la relation entre les deux partenaires BGP est explicitement marquée comme Client-Fournisseur ou Pair-Pair, facilitant la détection des fuites. Un concept qui était auparavant purement business, le type de relation entre acteurs BGP, passe ainsi dans le protocole.

Le RFC utilise donc ces termes, pour désigner la place d'un acteur BGP dans le processus de routage :

  • Fournisseur (Provider) : peut envoyer n'importe quelle route (un transitaire est un Fournisseur).
  • Client (Customer) : ne peut envoyer que ses propres routes, ou bien celles apprises d'un de ses clients (l'opérateur qui est Client pour une relation BGP peut être Fournisseur pour une autre).
  • Serveur de routes (Route Server) : peut envoyer toutes les routes qu'il connait à ses clients.
  • Client d'un serveur de routes (Route Server Client) : ne peut envoyer au serveur de routes que ses propres routes, ou bien celles apprises d'un de ses clients.
  • Pair (Peer) : ne peut envoyer à un de ses pairs que ses propres routes, ou bien celles apprises d'un de ses clients.

Une violation de ces règles est une fuite de routes (RFC 7908). Le RFC précise qu'il s'agit de relations « techniques », qu'elles n'impliquent pas forcément une relation business mais, en pratique, l'échange entre pairs est souvent gratuit et la relation Client-Fournisseur payante. Le RFC précise aussi qu'il peut exister des cas plus complexes, comme une relation Client-Fournisseur pour certains préfixes IP et Pair-Pair pour d'autres.

Bon, assez de politique et de business, place à la technique et au protocole. Le RFC définit une nouvelle capacité (RFC 5492) BGP, Role, code 9 et dont la valeur, stockée sur un octet, indique un des rôles listés ci-dessus (0 pour Fournisseur, 3 pour Client, 4 pour Pair, etc). D'autres rôles pourront être ajoutés dans le futur (politique « Examen par l'IETF », cf. RFC 8126). Ce rôle indique la place de l'AS et est configuré avec la session BGP (rappelez-vous qu'un AS peut être Client d'un côté et Fournisseur de l'autre). À l'ouverture de la connexion, les routeurs vérifient la compatibilité des rôles (si tous les deux annoncent qu'ils sont Fournisseur, c'est un problème, d'où un nouveau code d'erreur BGP, Role mismatch).

Une fois que les deux routeurs sont d'accord sur les rôles respectifs de leurs AS, les routes annoncées peuvent être marquées avec un nouvel attribut, OTC (Only To Customer, code 35), dont la valeur est un numéro d'AS. Une route ainsi marquée ne sera acceptée que si elle vient d'un Fournisseur, ou d'un pair dont le numéro d'AS coïncide. Autrement, elle sera considérée comme résultant d'une fuite.

Ah, et pour les cas compliqués dont on a parlé plus haut, comme les rôles différents pour chaque préfixe ? Dans ce cas-là, le mécanisme des rôles de ce RFC n'est pas adapté et ne doit pas être utilisé.

Ce système des rôles est apparemment mis en œuvre dans des patches (non encore intégrés ?) pour FRRouting et BIRD. Par exemple, pour FRRouting, on configurerait :

neighbor 2001:db8:999::1 local-role customer
  

Pour BIRD, cela serait :

configuration
    ...
    protocol bgp {
        ...
        local_role customer;
    }
  

Et la gestion des rôles devrait être bientôt dans BGPkit. Et une version antérieure à la sortie du RFC tourne sur les routeurs MikroTik (voir leur documentation, local.role ebgp-customer…). Par contre, je ne sais pas pour Cisco ou Juniper. Si quelqu'un a des informations…


Téléchargez le RFC 9234


L'article seul

RFC 9233: IDNA2008 and Unicode 12.0.0

Date de publication du RFC : Mars 2022
Auteur(s) du RFC : P. Faltstrom (Netnod)
Chemin des normes
Première rédaction de cet article le 24 avril 2022


La norme pour les noms de domaine en Unicode, dite « IDNA 2008 », prévoit une révision à chaque nouvelle version d'Unicode pour éventuellement s'adapter à des changements dus à ces nouvelles versions. Ce processus de révision a pas mal cafouillé (euphémisme), et ce RFC doit donc traiter d'un coup les versions 6 à 12 d'Unicode.

Le fond du problème est que l'ancienne norme sur les IDN (RFC 3490) était strictement liée à une version donnée d'Unicode et qu'il fallait donc une nouvelle norme pour chacune des versions annuelles d'Unicode. Vu le processus de publication d'une norme à l'IETF, ce n'était pas réaliste. La seconde norme IDN, « IDN 2 » ou « IDN 2008 » (bien qu'elle soit sortie en 2010) remplaçait les anciennes tables fixes de caractères autorisés ou interdits par un algorithme, à faire tourner à chaque sortie d'une version d'Unicode pour produire les tables listant les caractères qu'on peut accepter dans un nom de domaine internationalisé (le mécanisme exact, utilisant les propriétés des caractères listées dans la norme Unicode, figure dans le RFC 5892). En théorie, c'était très bien. En pratique, malgré les règles de stabilité d'Unicode, il se produisait parfois des problèmes. Comme le documente le RFC 8753, un caractère peut ainsi passer d'interdit à autorisé, ce qui n'est pas grave mais aussi dans certains cas d'autorisé à interdit ce qui est bien plus embêtant : que faire des noms déjà réservés qui utilisent ce caractère ? En général, il faut ajouter une exception manuelle, ce qui justifie un mécanisme de révision de la norme IDN, mis en place par ce RFC 8753. Ce nouveau RFC 9233 est le premier RFC de révision. Heureusement, cette fois, aucune exception manuelle n'a été nécessaire.

La précédente crise était due à Unicode version 6 qui avait créé trois incompatibilités (RFC 6452). Une seule était vraiment gênante, le caractère , qui, autorisé auparavant, était devenu interdit suite au changement de ses propriétés dans la norme Unicode. Le RFC 6452 avait documenté la décision de ne rien faire pour ce cas, ce caractère n'ayant apparemment jamais été utilisé. Unicode 7 a ajouté un autre problème, le , qui était un cas différent (la possibilité de l'écrire de plusieurs façons), et la décision a été prise de faire désormais un examen formel de chaque nouvelle version d'Unicode. Mais cet examen a été souvent retardé et voilà pourquoi, alors qu'Unicode 13 est sorti (ainsi qu'Unicode 14 depuis), ce nouveau RFC ne traite que jusqu'à la version 12.

Passons maintenant à l'examen des changements apportés par les versions 7 à 12 d'Unicode, fait en section 3 du RFC :

  • À part le cas du cité plus haut, Unicode 7 n'a pas apporté de changements gênants pour IDN (par exemple, U+17B4 (caractère non visible) a changé de propriétés mais il était interdit pour IDN et le reste),
  • Unicode 8, Unicode 9 et Unicode 10 n'ont appporté aucun changement gênant,
  • Unicode 11 a changé les propriétés de certains caractères existants mais le résultat pour IDN ne change pas (par exemple, 𑨇, qui était autorisé, le reste),
  • Et Unicode 12 ? Rien de problématique.

En Unicode 11, 𑇉 qui passe d'interdit à autorisé, était un cas peu gênant. Le RFC prend donc la décision de ne pas ajouter d'exception pour ce caractère peu commun.

Voilà, arrivé ici, vous pensez peut-être que cela fait beaucoup de bruit pour rien puisque finalement les différentes versions d'Unicode n'ont pas créé de problème. Mais c'est justement pour s'assurer de cela que cet examen était nécessaire.

Pour compliquer davantage les choses, on notera qu'il existe encore sans doute (section 2.3 du RFC) des déploiements d'IDN qui en sont restés à la première version (celle du RFC 3490) voire qui sont un mélange des deux versions d'IDN, en ce qui concerne l'acceptation ou le refus des caractères.

En avril 2022, le travail pour Unicode 13 ou Unicode 14 n'a apparemment pas encore commencé…


Téléchargez le RFC 9233


L'article seul

RFC 9230: Oblivious DNS over HTTPS

Date de publication du RFC : Juin 2022
Auteur(s) du RFC : E. Kinnear (Apple), P. McManus (Fastly), T. Pauly (Apple), T. Verma, C.A. Wood (Cloudflare)
Expérimental
Première rédaction de cet article le 9 juin 2022


Des protocoles de DNS sécurisés comme DoH protègent la liaison entre un client et un résolveur DNS. Mais le résolveur, lui, voit tout, aussi bien le nom de domaine demandé que l'adresse IP du client. Ce nouveau RFC décrit un mécanisme expérimental de relais entre le client et le serveur DoH, permettant de cacher l'adresse IP du client au serveur, et la requête au relais. Une sorte de Tor minimum.

Normalement, DoH (normalisé dans le RFC 8484) résout le problème de la surveillance des requêtes DNS. Sauf qu'évidemment le serveur DoH lui-même, le résolveur, voit le nom demandé, et la réponse faite. (Les utilisateurices de l'Internet oublient souvent que la cryptographie, par exemple avec TLS, protège certes contre le tiers qui écoute le réseau mais pas du tout contre le serveur avec qui on parle.) C'est certes nécessaire à son fonctionnement mais, comme il voit également l'adresse IP du client, il peut apprendre pas mal de choses sur le client. Une solution possible est d'utiliser DoH (ou DoT) sur Tor (exemple plus loin). Mais Tor est souvent lent, complexe et pas toujours disponible (cf. annexe A du RFC). D'où les recherches d'une solution plus légère, Oblivious DoH, décrit dans ce nouveau RFC. L'idée de base est de séparer le routage des messages (qui nécessite qu'on connaisse l'adresse IP du client) et la résolution DNS (qui nécessite qu'on connaisse la requête). Si ces deux fonctions sont assurées par deux machines indépendantes, on aura amélioré la protection de la vie privée.

Les deux machines vont donc être :

  • L'Oblivious Proxy, le relais qui connait l'adresse IP du client mais ne voit passer qu'une requête DNS chiffrée ; ce n'est pas un relais HTTP complet, il est spécialisé dans DoH.
  • Et l'Oblivious Target, le serveur DoH (le résolveur, donc) ; il a une clé publique avec laquelle le client chiffrera les requêtes à envoyer.

Un schéma simplifié : oblivious-doh

Il est important de répéter que, si le relais et le serveur sont complices, Oblivious DoH perd tout intérêt. Ils doivent donc être gérés par des organisations différentes.

La découverte du relais, et de la clé publique du serveur, n'est pas traité dans ce RFC. Au moins au début, elle se fera « manuellement ».

Le gros morceau du RFC est sa section 4. Elle explique les détails du protocole. Le client qui a une requête DNS à faire doit la chiffrer avec la clé publique du serveur. Ce client a été configuré avec un gabarit d'URI (RFC 6570) et avec l'URL du serveur DoH, serveur qui doit connaitre le mécanisme Oblivious DoH. Le gabarit doit contenir deux variables, targethost (qui sera incarné avec le nom du serveur DoH) et targetpath (qui sera incarné avec le chemin dans l'URL du serveur DoH). Par exemple, un gabarit peut être https://dnsproxy.example/{targethost}/{targetpath}. Le client va ensuite faire une requête (méthode HTTP POST) vers le relais. La requête aura le type application/oblivious-dns-message (la réponse également, donc le client a intérêt à indiquer Accept: application/oblivious-dns-message).

En supposant un serveur DoH d'URL https://dnstarget.example/dns, la requête sera donc (en utilisant la syntaxe de description de HTTP/2 et 3) :

:method = POST
:scheme = https
:authority = dnsproxy.example
:path = /dnstarget.example/dns
accept = application/oblivious-dns-message
content-type = application/oblivious-dns-message
content-length = 106

[La requête, chiffrée]
  

Le relais enverra alors au serveur :

:method = POST
:scheme = https
:authority = dnstarget.example
:path = /dns
accept = application/oblivious-dns-message
content-type = application/oblivious-dns-message
content-length = 106    

[La requête, chiffrée]
  

Le relais ne doit pas ajouter quelque chose qui permettrait d'identifier le client, ce qui annulerait tout l'intérêt d'Oblivious DoH. Ainsi, le champ forwarded (RFC 7239) est interdit. Et bien sûr, on n'envoie pas de cookies, non plus.

La réponse utilise le même type de médias :

:status = 200
content-type = application/oblivious-dns-message
content-length = 154

[La réponse, chiffrée]
  

Le relais, en la transmettant, ajoutera un champ proxy-status (RFC 9209).

Les messages de type application/oblivious-dns-message sont encodés ainsi (en utilisant le langage de description de TLS, cf. RFC 8446, section 3) :


struct {
      opaque dns_message<1..2^16-1>;
      opaque padding<0..2^16-1>;
} ObliviousDoHMessagePlaintext;

  

Bref, un message DNS binaire, et du remplissage. Le remplissage est nécessaire car TLS, par défaut, n'empêche pas l'analyse de trafic. (Lisez le RFC 8467.) Le tout est ensuite chiffré puis mis dans :


struct {
      uint8  message_type;
      opaque key_id<0..2^16-1>;
      opaque encrypted_message<1..2^16-1>;
} ObliviousDoHMessage;

  

Le key_id servant à identifier ensuite le matériel cryptographique utilisé.

J'ai parlé à plusieurs reprises de chiffrement. Le client doit connaitre la clé publique du serveur DoH pour pouvoir chiffrer. (Le RFC ne prévoit pas de mécanisme pour cela.) Comme avec la plupart des systèmes de chiffrement, le chiffrement sera en fait réalisé avec un algorithme symétrique. La transmission ou la génération de la clé de cet algorithme utlisera le mécanisme HPKE (Hybrid Public Key Encryption, spécifié dans le RFC 9180).

Outre l'analyse de trafic, citée plus haut, une potentielle faille d'Oblivious DoH serait le cas d'un serveur indiscret qui essaierait de déduire des informations de l'établissement des connexions DoH vers lui. Le RFC recommande que les relais établissent des connexions de longue durée et les réutilisent pour plusieurs clients, rendant cette éventuelle analyse plus complexe.

Notez aussi que le relais est évidemment libre de sa politique. Les relais peuvent par exemple ne relayer que vers certains serveurs DoH connus.

Le serveur DoH ne connait pas l'adresse IP du client (c'est le but) et ne peut donc pas transmettre aux serveurs faisant autorité les informations qui peuvent leur permettre d'envoyer une réponse adaptée au client final. Si c'est un problème, le client final peut toujours utiliser ECS (EDNS Client Subnet, RFC 7871), en indiquant un préfixe IP assez générique pour ne pas trop en révéler. Mais cela fait évidemment perdre une partie de l'intérêt d'Oblivious DoH.

Bon, et questions mises en œuvre de ce RFC ? Il y en a une dans DNScrypt (et ils publient une liste de serveurs et de relais).

Oblivious DoH est en travaux depuis un certain temps (voyez par exemple cet article de Cloudflare). Notez que la technique de ce RFC est spécifique à DoH. Il y a aussi des travaux en cours à l'IETF pour un mécanisme plus général, comme par exemple un Oblivious HTTP, qui serait un concurrent « low cost » de Tor.

À propos de Tor, j'avais dit plus haut qu'on pouvait évidemment, si on ne veut pas révéler son adresse IP au résolveur DoH, faire du DoH sur Tor. Voici un exemple de requête DoH faite avec le client kdig et le programme torsocks pour faire passer les requêtes TCP par Tor :

% torsocks kdig +https=/ @doh.bortzmeyer.fr ukraine.ua
;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
;; HTTP session (HTTP/2-POST)-(doh.bortzmeyer.fr/)-(status: 200)
...
;; ANSWER SECTION:
ukraine.ua.         	260	IN	A	104.18.7.16
ukraine.ua.         	260	IN	A	104.18.6.16
...
;; Received 468 B
;; Time 2022-06-02 18:45:15 UTC
;; From 193.70.85.11@443(TCP) in 313.9 ms
  

Et ce résolveur DoH ayant un service .onion, on peut même :

% torsocks kdig +https=/ @lani4a4fr33kqqjeiy3qubhfx2jewfd3aeaepuwzxrx6zywp2mo4cjad.onion ukraine.ua
;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
;; HTTP session (HTTP/2-POST)-(lani4a4fr33kqqjeiy3qubhfx2jewfd3aeaepuwzxrx6zywp2mo4cjad.onion/)-(status: 200)
...
;; ANSWER SECTION:
ukraine.ua.         	212	IN	A	104.18.6.16
ukraine.ua.         	212	IN	A	104.18.7.16
...
;; Received 468 B
;; Time 2022-06-02 18:46:00 UTC
;; From 127.42.42.0@443(TCP) in 1253.8 ms
  

Pour comparer les performances (on a dit que la latence de Tor était franchement élevée), voici une requête non-Tor vers le même résolveur :

% kdig +https=/ @doh.bortzmeyer.fr ukraine.ua                                             
;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
;; HTTP session (HTTP/2-POST)-([2001:41d0:302:2200::180]/)-(status: 200)
...
;; ANSWER SECTION:
ukraine.ua.         	300	IN	A	104.18.6.16
ukraine.ua.         	300	IN	A	104.18.7.16
...
;; Received 468 B
;; Time 2022-06-02 18:49:44 UTC
;; From 2001:41d0:302:2200::180@443(TCP) in 26.0 ms
  

Téléchargez le RFC 9230


L'article seul

RFC 9229: IPv4 Routes with an IPv6 Next Hop in the Babel Routing Protocol

Date de publication du RFC : Mai 2022
Auteur(s) du RFC : J. Chroboczek (IRIF, University of Paris)
Expérimental
Réalisé dans le cadre du groupe de travail IETF babel
Première rédaction de cet article le 11 mai 2022


Voici une extension sympathique qui réjouira tous les amateurs de routage IP. Elle permet à un routeur qui parle le protocole de routage Babel d'annoncer un préfixe IPv4 où le routeur suivant pour ce préfixe n'a pas d'adresse IPv4 sur cette interface.

Un petit rappel de routage : les annonces d'un routeur qui parle un protocole de routage dynamique comme Babel (RFC 8966) comprennent un préfixe IP (comme 2001:db8:543::/48) et l'adresse IP du routeur suivant (next hop dans la langue de Radia Perlman). Traditionnellement, le préfixe et l'adresse du routeur suivant étaient de la même version (famille) : tous les deux en IPv4 ou tous les deux en IPv6. Mais, si on veut router IPv6 et IPv4, cela consomme une adresse IP par interface sur chaque routeur, or les adresses IPv4 sont désormais une denrée très rare. D'où la proposition de ce RFC : permettre des annonces d'un préfixe IPv4 avec une adresse de routeur suivant en IPv6. (Notez que cela ne concerne pas que Babel, cela pourrait être utilisé pour d'autres protocoles de routage dynamique.)

La section 1 du RFC résume ce que fait un protocole de routage. Son but est de construire des tables de routage, où chaque entrée de la table est indexée par un préfixe d'adresses IP et a une valeur, l'adresse du routeur suivant. Par exemple :

   destination                      next hop
2001:db8:0:1::/64               fe80::1234:5678%eth0
203.0.113.0/24                  192.0.2.1    
  

Lorsqu'un routeur doit transmettre un paquet, il regarde dans cette table et trouve l'adresse du routeur suivant. Il va alors utiliser un protocole de résolution d'adresses (ARPRFC 826 - pour IPv4, NDRFC 4861 - pour IPv6) pour trouver l'adresse MAC du routeur suivant. Rien dans cette procédure n'impose que le préfixe de destination et l'adresse du routeur suivant soient de la même version d'IP. Un routeur qui veut transmettre un paquet IPv4 vers un routeur dont il ne connait que l'adresse IPv6 procède à la résolution en adresse MAC, puis met simplement le paquet IPv4 dans une trame portant l'adresse MAC en question (l'adresse IPv6 du routeur suivant n'apparait pas dans le paquet).

En permettant des annonces de préfixes IPv4 avec un routeur suivant en IPv6, on économise des adresses IPv4. Un réseau peut fournir de la connectivité IPv4 sans avoir d'adresses IPv4, à part au bord. Et comme les adresses IPv6 des routeurs sont des adresses locales au lien allouées automatiquement (cf. RFC 7404), on peut avoir un réseau dont le cœur n'a aucune adresse statique, ce qui peut faciliter sa gestion.

Notre RFC documente donc une extension au protocole Babel (qui est normalisé dans le RFC 8966), nommée v4-via-v6. (Comme dit plus haut, le principe n'est pas spécifique à Babel, voir le RFC 5549 pour son équivalent pour BGP.)

Bon, le concret, maintenant. Les préfixes annoncés en Babel sont précédés de l'AE (Address Encoding), un entier qui indique la version (famille) du préfixe. Ce RFC crée un nouvel AE, portant le numéro 4, qui a le même format que l'AE 1 qui servait à IPv4. Une annonce d'un préfixe ayant l'AE 4 devra donc contenir un préfixe IPv4 et un routeur suivant en IPv6. Un routeur qui sait router IPv4 mais n'a pas d'adresse IPv4 sur une de ses interfaces peut donc quand même y annoncer les préfixes connus, en les marquant avec l'AE 4 et en mettant comme adresse l'adresse IPv6 pour cette interface. (Cet AE est uniquement pour les préfixes, pas pour l'indication du routeur suivant.)

Les routeurs qui ne gèrent pas l'extension v4-via-v6 ignoreront cette annonce, comme ils doivent ignorer toutes les annonces portant un AE inconnu (RFC 8966). Pour cette raison, si un routeur a une adresse IPv4 sur une interface, il faut qu'il utilise des annonces traditionnelles, avec l'AE 1, pour maximiser les chances que son annonce soit acceptée.

Arrivé ici, mes lectrices et mes lecteurs, qui sont très malins, se demandent depuis longtemps « mais un routeur doit parfois émettre des erreurs ICMP (RFC 792), par exemple s'il n'a pas d'entrée dans sa table pour cette destination ». Comment peut-il faire s'il n'a pas d'adresse sur l'interface où il a reçu le paquet coupable ? Ne rien émettre n'est pas acceptable, certains protocoles en dépendent. C'est par exemple le cas de la découverte de la MTU du chemin, documentée dans le RFC 1191, qui a besoin des messages ICMP Packet too big. Certes, il existe un algorithme de découverte de cette MTU du chemin qui est entièrement de bout en bout, et ne dépend donc pas des routeurs et de leurs messages (RFC 4821). Mais ICMP sert à d'autres choses, on ne peut pas y renoncer.

Si le routeur Babel a une adresse IPv4 quelque part, sur une de ses interfaces, il peut l'utiliser comme adresse IP source pour ses messages ICMP. S'il n'en a pas, il peut toujours utiliser 192.0.0.8 comme suggéré par la section 4.8 du RFC 7600. Bien sûr, si tout le monde le fait, des outils d'administration du réseau très pratiques comme traceroute seront moins utiles.

La nouvelle extension peut soulever des questions de sécurité (section 6 du RFC). Par exemple, si l'administrateur réseaux croyait que, parce que les routeurs n'avaient pas d'adresse IPv4 sur une interface, les paquets IPv4 ne seraient pas traités sur cette interface, cette supposition n'est plus vraie. Ainsi, une île de machines IPv4 qui se croyaient séparées de l'Internet IPv4 par un ensemble de routeurs qui n'avaient pas d'adresse IPv4 de leur côté peut se retrouver subitement connectée. Si ce n'est pas ce qu'on veut, il faut penser à ne pas se contenter du protocole de routage pour filtrer, mais aussi à filtrer explicitement IPv4.

Question mise en œuvre, cette extension figure dans babeld (voir ici un compte-rendu d'expérience) à partir de la version 1.12, ainsi que dans BIRD.


Téléchargez le RFC 9229


L'article seul

RFC 9228: Delivered-To Email Header Field

Date de publication du RFC : Avril 2022
Auteur(s) du RFC : D. Crocker (Brandenburg InternetWorking)
Expérimental
Première rédaction de cet article le 14 avril 2022


Le courrier électronique est souvent plus compliqué et plus varié que ne le croient certains utilisateurs. Par exemple, rien ne garantit que l'adresse à laquelle un message est livré soit listée dans les champs de l'en-tête (RFC 5322) comme To: ou Cc:. Cette adresse de destination est en effet séparément gérée, dans le protocole SMTP (RFC 5321) et ses comandes RCPT TO. Et puis un message peut être relayé, il peut y avoir plusieurs livraisons successives. Bref, quand on a un message dans sa boite aux lettres, on ne sait pas forcément quelle adresse avait servi. D'où ce RFC expérimental qui propose d'élargir le rôle du champ Delivered-To: pour en faire un bon outil d'information.

Ce champ existe déjà et on le voit parfois apparaitre dans les en-têtes, par exemple dans un message que je viens de recevoir :

Delivered-To: monpseudo@sources.org
  

Mais il n'est pas vraiment normalisé, son contenu exact peut varier et, en pratique, lorsqu'il y a eu plusieurs délivrances successives suivies de transmissions à une autre adresse, il ne reste parfois qu'une occurrence de ce champ, la plus récente. Difficile donc de compter dessus.

Mais c'est quoi, la livraison d'un message ? Comme définie par le RFC 5598, dans sa section 4.3.3, c'est un transfert de responsabilité d'un MTA vers un MDA comme procmail. Il peut y avoir plusieurs délivrances successives par exemple si le MDA, au lieu de simplement écrire dans la boite aux lettres de l'utilisateur, fait suivre le message à une autre adresse. Un exemple typique est celui d'une liste de diffusion, où le message va être délivré à la liste puis, après transmission par le logiciel de gestion de listes, à chaque utilisateur (dont les MDA renverront peut-être le message, créant de nouvelles livraisons).

Pourquoi est-il utile d'avoir de l'information sur ces livraisons successives ? Outre la curiosité, une motivation importante est le débogage. S'il y a des problèmes de livraison, la traçabilité est essentielle à l'analyse. Une autre motivation est la détection automatique de boucles, un problème souvent compliqué.

On l'a dit, Delivered-To: existe déjà, même s'il n'était normalisé dans aucun RFC, bien qu'il soit cité dans des exemples, comme dans le RFC 5193. Ce RFC 9228 enregistre le champ (selon la procédure du RFC 3864), et prévoit d'étendre son application. (Une tentative de normalisation est en cours dans draft-duklev-deliveredto.) Du fait de cette absence de normalisation, il existe de nombreuses variations dans l'utilisation de Delivered-To:. Par exemple, si la plupart des logiciels mettent comme valeur une adresse de courrier et rien d'autre, certains ajoutent des commentaires. Notons que le champ Received: est souvent utilisé de la même façon, via sa clause for. On peut ainsi trouver (regardez le for) :

    
Received: from bendel.debian.org (bendel.debian.org [82.195.75.100])
        (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
        key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
        (No client certificate requested)
        by ayla.bortzmeyer.org (Postfix) with ESMTPS id 6D245A00D9
        for <monpseudo@sources.org>; Wed, 16 Feb 2022 13:22:03 +0100 (CET)

  

Là encore, il n'y a pas de normalisation et il semble qu'on ne puisse pas compter sur cette clause for. Par exemple, elle n'indique pas forcément une livraison mais peut être utilisée pour une simple transmission d'un serveur à un autre.

Venons-en à la définition de l'utilisation de Delivered-To: proposée par notre RFC dans sa section 4 :

  • Le champ Delivered-To: est ajouté uniquement lors d'une livraison,
  • sa valeur est l'adresse de courrier utilisée pour cette livraison (une adresse unique, jamais de liste),
  • si une réécriture de l'adresse est faite, on rajoute un Delivered-To: avec l'adresse réécrite,
  • comme il peut y avoir plusieurs Delivered-To: (en cas de livraisons multiples), le logiciel qui en ajoute un doit le mettre en haut (comme pour les autres champs de trace de l'en-tête, par exemple Received:, cf. RFC 5321, section, 4.1.1.4), l'ordre chronologique des Delivered-To: sera donc de bas en haut,
  • et donc les logiciels ne doivent pas réordonner les Delivered-To: ou en supprimer, sous peine de casser cette chronologie.

En conséquence, si un logiciel qui va ajouter un Delivered-To: avec une certaine adresse voit un Delivered-To: avec la même adresse, il peut être sûr qu'il y a une boucle. Voici un exemple de deux Delivered-To: différents, en raison du passage par une liste de diffusion (le message comportait évidemment bien d'autres champs dans son en-tête). La liste a été la première à traiter le message, la livraison finale ayant eu lieu après (donc plus haut dans le message, cf. section 5 du RFC) :

Delivered-To: monpseudo@sources.org
Delivered-To: lists-debian-user-french@bendel.debian.org
  

Le RFC donne un exemple imaginaire mais plus détaillé (j'ai raccourci les exemples du RFC, référez-vous à lui pour avoir les Received: et autres détails). D'abord, le message est émis par Ann :


From: Ann Author <aauthor@com.example>
Date: Mon, 25 Jan 2021 18:29:00 -0500
To: list@org.example
Subject: Sending through a list and alias
Sender: Ann Author <aauthor@com.example>   

  

Ce message a été envoyé à une liste de diffusion. Il est donc désormais :


Delivered-To: list@org.example
From: Ann Author <aauthor@com.example>
Date: Mon, 25 Jan 2021 18:29:06 -0500
To: list@org.example
Subject: Sending through a list and alias

  

Un des abonnés à la liste est alumn@edu.example. Chez lui, le message sera :


Delivered-To: alumn@edu.example
Delivered-To: list@org.example
From: Ann Author <aauthor@com.example>
Date: Mon, 25 Jan 2021 18:29:06 -0500
To: list@org.example
Subject: Sending through a list and alias

Et cet abonné fait suivre automatiquement son courrier à theRecipient@example.net. Le message, dans son état final, sera :

  

Delivered-To: theRecipient@example.net
Delivered-To: alumn@edu.example
Delivered-To: list@org.example
From: Ann Author <aauthor@com.example>
Date: Mon, 25 Jan 2021 18:29:06 -0500
To: list@org.example
Subject: Sending through a list and alias

Voilà, vous savez tout désormais sur l'extension proposée de l'utilisation de Delivered-To:. La section 6 attire toutefois notre attention sur quelques risques. Delivered-To: a comme valeur une donnée personnelle. Donc, attention, cela peut poser des questions de vie privée. Par exemple, la liste des adresses par lesquelles est passé un message peut en révéler plus que ce que le destinataire connaissait. Le problème risque surtout de se poser si quelqu'un fait suivre manuellement un message en incluant tous les en-têtes.

Autre problème potentiel, certains systèmes stockent les messages identiques en un seul exemplaire. Si on écrit à alice@example.com et bob@example.com, le serveur d'example.com pourrait décider de ne stocker qu'un seul exemplaire du message, avec des liens depuis les boites aux lettres d'Alice et Bob. Ce serait évidemment incompatible avec Delivered-To: (et pas question de mettre deux Delivered-To:, on ne veut pas révéler à Alice que Bob a reçu le message et réciproquement).


Téléchargez le RFC 9228


L'article seul

RFC 9224: Finding the Authoritative Registration Data Access Protocol (RDAP) Service

Date de publication du RFC : Mars 2022
Auteur(s) du RFC : M. Blanchet (Viagenie)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 25 avril 2022


Le protocole RDAP d'accès à l'information sur les objets (noms de domaines, adresses IP, etc) stockés dans un registre fonctionne en interrogeant le serveur en HTTP. Encore faut-il trouver le serveur à interroger. Comment le client RDAP qui veut des informations sur 2001:67c:288::2 sait-il à quel serveur demander ? Ce RFC décrit le mécanisme choisi. En gros, l'IANA gère des « méta-services » qui donnent la liste de serveurs RDAP, mais il peut aussi y avoir redirection d'un serveur RDAP vers un autre. Ce RFC remplace le premier RFC sur le sujet, le RFC 7484, avec très peu de changements.

Le protocole RDAP est décrit entre autres dans le RFC 7480 qui précise comment utiliser HTTP pour transporter les requêtes et réponses RDAP. Comme indiqué plus haut, il ne précise pas comment trouver le serveur RDAP responsable d'un objet donné. Le prédécesseur de RDAP, whois, avait exactement le même problème, résolu par exemple en mettant en dur dans le logiciel client tous les serveurs whois connus. En pratique, beaucoup de gens font simplement une requête Google et accordent une confiance aveugle auu premier résultat venu, quel que soit sa source. Au contraire, ce RFC vise à fournir un mécanisme pour trouver la source faisant autorité.

Avant d'exposer la solution utilisée, la section 1 de notre RFC rappelle comment ces objets (noms de domaine, adresses IP, numéros d'AS, etc) sont alloués et délégués. L'IANA délégue les TLD aux registres de noms de domaines, des grands préfixes IP et les blocs de numéros d'AS aux RIR. Il est donc logique que RDAP suive le même chemin : pour trouver le serveur responsable, on commence par demander à l'IANA, qui a déjà les processus pour cela, et maintient les registres correspondants (adresses IPv4, adresses IPv6, numéros d'AS et domaines).

Pour permettre à RDAP de fonctionner, ce RFC demande donc à l'IANA quelques fichiers supplémentaires, au format JSON (RFC 8259), dont le contenu dérive des registres cités plus haut. Et l'IANA doit aussi prévoir des serveurs adaptés à servir ces fichiers, nommés RDAP Bootstrap Service, à de nombreux clients RDAP.

Le format de ces fichiers JSON est décrit dans la section 3 de notre RFC. Chaque bootstrap service contient des métadonnées comme un numéro de version et une date de publication, et il contient un membre nommé services qui indique les URL où aller chercher l'information (la syntaxe formelle figure en section 10). Voici un extrait d'un des fichiers actuels :

 
% curl https://data.iana.org/rdap/ipv4.json
  "description": "RDAP bootstrap file for IPv4 address allocations",
  "publication": "2019-06-07T19:00:02Z",
  "services": [
    [
      [
        "41.0.0.0/8",
        "102.0.0.0/8",
        "105.0.0.0/8",
        "154.0.0.0/8",
        "196.0.0.0/8",
        "197.0.0.0/8"
      ],
      [
        "https://rdap.afrinic.net/rdap/",
        "http://rdap.afrinic.net/rdap/"
      ]
    ],
    ...

On voit que le contenu est un tableau donnant, pour des objets donnés, les URL des serveurs RDAP à contacter pour ces objets indiqués, par exemple pour l'adresse IP 154.3.2.1, on ira demander au serveur RDAP d'AfriNIC en https://rdap.afrinic.net/rdap/.

Le membre publication indique la date de parution au format du RFC 3339. Les URL indiqués se terminent par une barre oblique, le client RDAP a juste à leur ajouter une requête formulée selon la syntaxe du RFC 9082. Ainsi, cherchant des informations sur 192.0.2.24, on ajoutera ip/192.0.2.24 à l'URL de base.

Pour les adresses IP (section 5), les entrées sont des préfixes, comme dans le premier exemple montré plus haut et la correspondance se fait avec le préfixe le plus spécifique (comme pour le routage IP). Les préfixes IPv4 suivent la syntaxe du RFC 4632 et les IPv6 celle du RFC 5952. Voici un exemple IPv6 (légèrement modifié d'un cas réel) :

   [
      [
        "2001:200::/23",
        "2001:4400::/23",
        "2001:8000::/19",
        "2001:a000::/20",
        "2001:b000::/20",
        "2001:c00::/23",
        "2001:e00::/23",
        "2400::/12"
      ],
      [
        "https://rdap.apnic.net/"
      ]
    ],
    [
      [
        "2001:200:1000::/36"
      ],
      [
        "https://example.net/rdaprir2/"
      ]

Si on cherche de l'information sur le préfixe 2001:200:1000::/48, il correspond à deux entrées dans le tableau des services (2001:200::/23 et 2001:200:1000::/36) mais la règle du préfixe le plus long (le plus spécifique) fait qu'on va utiliser 2001:200:1000::/36, et la requête finale sera donc https://example.net/rdaprir2/ip/2001:200:1000::/48.

Pour les domaines (section 4), les entrées des services sont des noms de domaine :

...
       "services": [
         [
           ["net", "com", "org"],
           [
             "https://registry.example.com/myrdap/"
           ]
         ],
         [
           ["foobar.org", "mytld"],
           [
             "https://example.org/"
           ]
         ],
...

L'entrée sélectionnée est la plus longue (en nombre de composants du nom, pas en nombre de caractères) qui correspond. Dans l'exemple ci-dessus, si on cherche des informations sur foobar.org, on ira voir le serveur RDAP en https://example.org/, si on en cherche sur toto.org, on ira en https://registry.example.com/myrdap/. (En pratique, aujourd'hui, le tableau des serveurs RDAP ne contient que des TLD.)

Pour les numéros d'AS (section 5.3), on se sert d'intervalles de numéros et de la syntaxe du RFC 5396 :

"services": [
         [
           ["2045-2045"],
           [
             "https://rir3.example.com/myrdap/"
           ]
         ],
         [
           ["10000-12000", "300000-400000"],
           [
             "http://example.org/"
           ]
         ],
...

Les entités (section 6 de notre RFC) posent un problème particulier, elles ne se situent pas dans un espace arborescent, contrairement aux autres objets utilisable avec RDAP. (Rappel : les entités sont les contacts, les titulaires, les BE…) Il n'existe donc pas de bootstrap service pour les entités (ni, d'ailleurs, pour les serveurs de noms, cf. section 9). En pratique, si une requête RDAP renvoie une réponse incluant un handle pour une entité, il n'est pas idiot d'envoyer les requêtes d'information sur cette entité au même serveur RDAP mais il n'est pas garanti que cela marche.

Notez (section 7) que le tableau services ne sera pas forcément complet et que certains objets peuvent ne pas s'y trouver. Par exemple, dans un tableau pour les TLD, les registres n'ayant pas encore de serveur RDAP ne seront logiquement pas cités. On peut toujours tenter un autre serveur, en espérant qu'il utilisera les redirections HTTP. Par exemple, ici, on demande au serveur RDAP de l'APNIC pour une adresse RIPE. On est bien redirigé avec un code 301 (RFC 7231, section 6.4.2) :


%  curl -v http://rdap.apnic.net/ip/2001:67c:288::2
...
> GET /ip/2001:67c:288::2 HTTP/1.1
> User-Agent: curl/7.38.0
> Host: rdap.apnic.net
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Date: Wed, 01 Apr 2015 13:07:00 GMT
< Location: http://rdap.db.ripe.net/ip/2001:67c:288::2
< Content-Type: application/rdap+json
...

La section 8 couvre quelques détails liés au déploiement de ce service. C'est que le Bootstrap Service est différent des autres registres IANA. Ces autres registres ne sont consultés que rarement (par exemple, lors de l'écriture d'un logiciel) et jamais en temps réel. Si le serveur HTTP de l'IANA plante, ce n'est donc pas trop grave. Au contraire, le Bootstrap Service doit marcher en permanence, car un client RDAP en a besoin. Pour limiter la pression sur les serveurs de l'IANA, notre RFC recommande que les clients RDAP ne consultent pas ce service à chaque requête RDAP, mais qu'au contraire ils mémorisent le JSON récupéré, en utilisant le champ Expires: de HTTP (RFC 7234, section 5.3) pour déterminer combien de temps doit durer cette mémorisation. Néanmoins, l'IANA a dû ajuster ses serveurs HTTP et louer les services d'un CDN pour assurer ce rôle relativement nouveau.

Le client RDAP doit d'autre part être conscient que le registre n'est pas mis à jour instantanément. Par exemple, si un nouveau TLD est ajouté par le gouvernement états-unien, via Verisign, TLD dont le registre a un serveur RDAP, cette information ne sera pas disponible immédiatement pour tous les clients RDAP.

Comme son ancêtre whois, RDAP soulève plein de questions de sécurité intéressantes, détaillées plus précisement dans le RFC 7481.

La section 12 de notre RFC détaille les processus IANA à l'œuvre. En effet, et c'est une autre différence avec les registres IANA habituels, il n'y a pas de mise à jour explicite des registres du bootstrap service, ils sont mis à jour implicitement comme effet de bord des allocations et modifications d'allocation dans les registres d'adresses IPv4, adresses IPv6, numéros d'AS et domaines. Il a juste fallu modifier les procédures de gestion de ces registres existants, pour permettre d'indiquer le serveur RDAP. Ainsi, le formulaire de gestion d'un TLD par son responsable a été modifié pour ajouter un champ "serveur RDAP" comme il y avait un champ "serveur Whois".

Aujourd'hui, les fichiers de ce service sont :

  • https://data.iana.org/rdap/dns.json
  • https://data.iana.org/rdap/ipv4.json
  • https://data.iana.org/rdap/ipv6.json
  • https://data.iana.org/rdap/asn.json

Voici, par exemple, celui d'IPv6 (notez le champ Expires: dans la réponse) :


%  curl -v https://data.iana.org/rdap/ipv6.json
...
* Connected to data.iana.org (2606:2800:11f:bb5:f27:227f:1bbf:a0e) port 80 (#0)
> GET /rdap/ipv6.json HTTP/1.1
> Host: data.iana.org
> User-Agent: curl/7.68.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Expires: Tue, 26 Apr 2022 12:30:52 GMT
< Last-Modified: Wed, 06 Nov 2019 19:00:04 GMT
...
{
  "description": "RDAP bootstrap file for IPv6 address allocations",
  "publication": "2019-11-06T19:00:04Z",
  "services": [
    [
      [
        "2001:4200::/23",
        "2c00::/12"
      ],
      [
        "https://rdap.afrinic.net/rdap/",
        "http://rdap.afrinic.net/rdap/"
...

Et celui des TLD :


% curl -v http://data.iana.org/rdap/dns.json
...
  "description": "RDAP bootstrap file for Domain Name System registrations",
  "publication": "2022-04-22T16:00:01Z",
  "services": [
    [
      [
        "nowruz",
        "pars",
        "shia",
        "tci",
        "xn--mgbt3dhd"
      ],
      [
        "https://api.rdap.agitsys.net/"
      ]
    ],

Testons si cela marche vraiment :

% curl -v https://api.rdap.agitsys.net/domain/www.nowruz
...
 "nameservers": [
    {
      "objectClassName": "nameserver",
      "ldhName": "ns1.aftermarket.pl.",
      "unicodeName": "ns1.aftermarket.pl."
    },
    {
      "objectClassName": "nameserver",
      "ldhName": "ns2.aftermarket.pl.",
      "unicodeName": "ns2.aftermarket.pl."
    }
  ]

Parfait, tout marche.

Il y a très peu de changements depuis le RFC précédent, le RFC 7484, quelques nettoyages seulement, et un changement de statut sur le chemin des normes.

Si vous aimez lire des programmes, j'ai fait deux mises en œuvre de ce RFC, la première en Python est incluse dans le code utilisé pour mon article « Récupérer la date d'expiration d'un domaine en RDAP ». Le code principal est ianardap.py mais vous noterez que, contrairement à ce que demande le RFC dans sa section 8, la mémorisation du bootstreap registry n'utilise pas le champ HTTP Expires:. La deuxième, en Elixir, est plus correcte et est disponible dans find-rdap-elixir.tar. Lorsque la constante @verbose est vraie, le programme affiche les étapes. S'il n'a pas mémorisé de données :

% mix run find_rdap.exs quimper.bzh
Starting
No expiration known
Retrieving from https://data.iana.org/rdap/dns.json
Updating the cache
RDAP bootstrap file for Domain Name System registrations published on 2022-04-22T16:00:01Z
RDAP server for quimper.bzh is https://rdap.nic.bzh/
Retrieving RDAP info at https://rdap.nic.bzh/domain/quimper.bzh
{"rdapConformance":["rdap_level_0","...
  

S'il en a mémorisé :

% mix run find_rdap.exs quimper.bzh
Starting
Expiration is 2022-04-26 12:44:48Z
Using the cache ./iana-rdap-bootstrap.json
RDAP bootstrap file for Domain Name System registrations published on 2022-04-22T16:00:01Z
RDAP server for quimper.bzh is https://rdap.nic.bzh/
Retrieving RDAP info at https://rdap.nic.bzh/domain/quimper.bzh
{"rdapConformance":["rdap_level_0", ...

Mais il existe évidemment une mise en œuvre de ce RFC dans tous les clients RDAP comme :

  • L'application de Viagenie,
  • Celle de l'ICANN (application Web),
  • L'ARIN a un service de redirection, https://rdap-bootstrap.arin.net/bootstrap, qui lit la base IANA et envoie les redirections appropriées, quand un client RDAP l'interroge.

Téléchargez le RFC 9224


L'article seul

RFC 9221: An Unreliable Datagram Extension to QUIC

Date de publication du RFC : Mars 2022
Auteur(s) du RFC : T. Pauly, E. Kinnear (Apple), D. Schinazi (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF quic
Première rédaction de cet article le 1 avril 2022


Le protocole QUIC fournit, comme son concurrent TCP, un canal fiable d'envoi de données. Mais certaines applications n'ont pas impérativement besoin de cette fiabilité et se contenteraient très bien d'un service de type datagramme. Ce nouveau RFC ajoute donc un nouveau type de trame QUIC, DATAGRAM, pour fournir un tel service.

Dit comme cela, évidemment, ça semble drôle, de bâtir un service non fiable au-dessus du service fiable que rend QUIC. Mais cela permet plusieurs choses très intéressantes, notamment si deux machines qui communiquent ont besoin des deux types de service : elles pourront utiliser une seule session QUIC pour les deux.

Un petit rappel (mais relisez le RFC 9000 pour les détails) : dans une connexion QUIC sont envoyés des paquets et chaque paquet QUIC contient une ou plusieurs trames. Chaque trame a un type et ce type indique si la trame sera retransmise ou non en cas de perte. Le service habituel (utilisé par exemple par HTTP/3) se sert de trames de type STREAM, qui fournissent un service fiable (les données perdues sont retransmises par QUIC). Mais toutes les applications ne veulent pas d'une telle fiabilité ou, plus précisement, ne sont pas prêtes à en payer le coût en termes de performance. Ces applications sont celles qui préféraient utiliser UDP (RFC 768) plutôt que TCP. Pour rajouter de la sécurité, elles utilisaient DTLS (RFC 6347). Si QUIC remplace TCP, comment remplacer UDP+DTLS ?

D'ailleurs, faut-il le remplacer ? La section 2 du RFC donne les raisons suivantes :

  • Les applications qui ont besoin à la fois d'un canal fiable et d'un canal qui ne l'est pas (par exemple une application de communication qui veut un canal fiable pour la signalisation et un pas forcément fiable pour les données multimédia) peuvent avec QUIC n'avoir qu'une seule session (et donc ne « payer » l'établissement de la session qu'une fois, ce qui diminuera la latence),
  • QUIC a un meilleur mécanisme de récupération des pertes que DTLS, lors de l'établissement de la connexion,
  • contrairement à UDP+DTLS, QUIC a un mécanisme de contrôle de la congestion, ce qui simplifie la tâche de l'application (cf. RFC 8085).

Ces raisons sont particulièrement importantes pour le streaming audio/vidéo, pour les jeux en ligne, etc. Les datagrammes dans QUIC peuvent aussi être utiles pour un service de VPN. Là aussi, le VPN a besoin à la fois d'un canal fiable pour la configuration de la communication, mais peut se satisfaire d'un service de type datagramme ensuite (puisque les machines connectées via le VPN auront leur propre mécanisme de récupération des données perdues). Permettre la création de VPN au-dessus de QUIC est le projet du groupe de travail MASQUE.

Bien, normalement, maintenant, vous êtes convaincu·es de l'intérêt des datagrammes au-dessus de QUIC. Pour en envoyer, il faut toutefois que les deux parties qui communiquent soient d'accord. C'est le rôle du paramètre de transport QUIC max_datagram_frame_size, à utiliser lors de l'établissement de la session. (Ce paramètre est enregistré à l'IANA.) S'il est absent, on ne peut pas envoyer de datagrammes (et le partenaire coupe la connexion si on le fait quand même). S'il est présent, il indique la taille maximale acceptée, typiquement 65 535 octets. Sa valeur peut être stockée dans la mémoire d'une machine, de façon à permettre son utilisation lors du 0-RTT (commencer une session QUIC directement sans perdre du temps en négociations). Un paquet QUIC 0-RTT peut donc inclure des trames de type DATAGRAM.

Ah, justement, ce type DATAGRAM. Désormais enregistré à l'IANA parmi les types de trames QUIC, il sert à indiquer des trames qui seront traitées comme des datagrammes, et qui ont la forme suivant :

DATAGRAM Frame {
     Type (i) = 0x30..0x31,
     [Length (i)],
     Datagram Data (..),
}
  

Le champ de longueur est optionnel, sa présence est indiquée par le dernier bit du type (0x30, pas de champ Longueur, 0x31, il y en a un). S'il est absent, la trame va jusqu'au bout du paquet QUIC (rappelons qu'un paquet QUIC peut contenir plusieurs trames).

Maintenant qu'on a des datagrammes comme QUIC, comment les utilise-t-on (section 5 du RFC) ? Rien d'extraordinaire, l'application envoie une trame DATAGRAM et l'application à l'autre bout la recevra. Si la trame n'est pas arrivée, l'émetteur ne le saura pas. La trame peut se retrouver avec d'autres trames (du même type ou pas) dans un même paquet QUIC (et plusieurs paquets QUIC peuvent se retrouver dans un même datagramme IP). La notion de ruisseau (stream) n'existe pas pour les trames-datagrammes, si on veut pouvoir les démultiplexer, c'est à l'application de se débrouiller. (Une version préliminaire de cette extension à QUIC prévoyait un mécanisme de démultiplexage, finalement abandonné.) Comme toutes les trames QUIC, les trames DATAGRAM sont protégées par la cryptographie. Le service est donc équivalent à celui d'UDP+DTLS, pas UDP seul.

Un service de datagrammes est non fiable, des données peuvent se perdre. Mais si les trames de type DATAGRAM ne sont pas réémises (pas par QUIC, en tout cas), elles entrainent néanmoins l'émission d'accusés de réception (RFC 9002, sections 2 et 3). Une bibliothèque QUIC peut ainsi (mais ce n'est pas obligatoire) notifier l'application des pertes. De la même façon, elle a la possibilité de dire à l'application quelles données ont été reçues par le QUIC à l'autre bout (ce qui ne garantit pas que l'application à l'autre bout les aient bien reçues, si on a besoin d'informations sûres, il faut le faire au niveau applicatif).

Comme l'émetteur n'estime pas crucial que toutes les données arrivent, une optimisation possible pour QUIC est que l'accusé de réception d'un paquet ne contenant que des trames DATAGRAM ne soit pas émis tout de suite, il peut attendre le paquet suivant.

Cette extension existe déjà dans plusieurs mises en œuvre de QUIC, mais je ne l'ai pas testée.


Téléchargez le RFC 9221


L'article seul

RFC 9211: The Cache-Status HTTP Response Header Field

Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Nottingham (Fastly)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 9 juin 2022


Ce RFC normalise enfin un en-tête HTTP pour permettre aux relais HTTP qui mémorisent les réponses reçues, les caches, d'indiquer au client comment ils ont traité une requête et si, par exemple, la réponse vient de la mémoire du relais ou bien s'il a fallu aller la récupérer sur le serveur HTTP d'origine.

Il existait déjà des en-têtes de débogage analogues mais non-standards, variant aussi bien dans leur syntaxe que dans leur sémantique. Le nouvel en-tête standard, Cache-Status: va, espérons-le, mettre de l'ordre à ce sujet. Sa syntaxe suit les principes des en-têtes structurés du RFC 8941. Sa valeur est donc une liste de relais qui ont traité la requête, séparés par des virgules, dans l'ordre des traitements. Le premier relais indiqué dans un Cache-Status: est donc le plus proche du serveur d'origine et le dernier relais étant le plus proche de l'utilisateur. Voici un exemple avec un seul relais, nommé cache.example.net :

Cache-Status: cache.example.net; hit
  

et un exemple avec deux relais, OriginCache puis CDN Company Here :

Cache-Status: OriginCache; hit; ttl=1100, "CDN Company Here"; hit; ttl=545  
  

La description complète de la syntaxe figure dans la section 2 de notre RFC. D'abord, il faut noter qu'un relais n'est pas obligé d'ajouter cet en-tête. Il le fait selon des critères qui lui sont propres. Le RFC déconseille fortement d'ajouter cet en-tête si la réponse a été générée localement par le relais (par exemple un 400 qui indique que le relais a détecté une requête invalide). En tout cas, le relais qui s'ajoute à la liste ne doit pas modifier les éventuels relais précédents. Chaque relais indique son nom, par exemple par un nom de domaine mais ce n'est pas obligatoire. Ce nom peut ensuite être suivi de paramètres, séparés par des point-virgules.

Dans le premier exemple ci-dessus, hit était un paramètre du relais cache.example.net. Sa présence indique qu'une réponse était déjà mémorisée par le relais et qu'elle a été utilisée (hit : succès, on a trouvé), le serveur d'origine n'ayant pas été contacté. S'il y avait une réponse stockée, mais qu'elle était rassise (RFC 9111, section 4.2) et qu'il a fallu la revalider auprès du serveur d'origine, hit ne doit pas être utilisé.

Le deuxième paramètre possible est fwd (pour Forward) et il indique qu'il a fallu contacter le serveur HTTP d'origine. Plusieurs raisons sont possibles pour cela, entre autres :

  • bypass, quand le relais a été configuré pour que cette requête soit systématiquement relayée,
  • method parce que la méthode HTTP utilisée doit être relayée (c'est typiquement le cas d'un PUT, cf. RFC 9110, section 9.3.4),
  • miss, un manque, la ressource n'était pas dans la mémoire du relais (notez qu'il existe aussi deux paramètres plus spécifiques, uri-miss et vary-miss),
  • stale, car la ressource était bien dans la mémoire du relais mais, trop ancienne et rassise, a dû être revalidée (RFC 9111, section 4.3) auprès du serveur d'origine.

Ainsi, cet en-tête :

Cache-Status: ExampleCache; fwd=miss; stored
  

indique que l'information n'était pas dans la mémoire de ExampleCache et qu'il a donc dû faire suivre au serveur d'origine (puis qu'il a stocké cette information dans sa mémoire, le paramètre stored).

Autre paramètre utile, ttl, qui indique pendant combien de temps l'information peut être gardée (cf. RFC 9111, section 4.2.1), selon les calculs du relais. Quant à detail, il permet de donner des informations supplémentaires, de manière non normalisée (la valeur de ce paramètre ne peut être interprétée que si on connait le programme qui l'a produite).

Les paramètres sont enregistrés à l'IANA, dans un nouveau registre, et de nouveaux paramètres peuvent y être ajoutés, suivant la politique « Examen par un expert » du RFC 8126. La recommandation est de n'enregistrer que des paramètres d'usage général, non spécifiques à un programme particulier, sauf à nommer le paramètre d'une manière qui identifie clairement sa spécificité.

Questions mises en œuvre, Squid sait renvoyer cet en-tête (cf. le code qui commence à SBuf cacheStatus(uniqueHostname()); dans le fichier src/client_side_reply.cc). Ce code n'est pas encore présent dans la version 5.2 de Squid, qui n'utilise encore que d'anciens en-têtes non-standards, il faut attendre de nouvelles versions :

< X-Cache: MISS from myserver
< X-Cache-Lookup: NONE from myserver:3128
< Via: 1.1 myserver (squid/5.2)
  

Je n'ai pas regardé les autres logiciels.


Téléchargez le RFC 9211


L'article seul

RFC 9210: DNS Transport over TCP - Operational Requirements

Date de publication du RFC : Mars 2022
Auteur(s) du RFC : J. Kristoff (Dataplane.org), D. Wessels (Verisign)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 15 avril 2022


Ce nouveau RFC décrit les exigences opérationnelles pour le DNS, plus précisement pour son protocole de transport TCP. Le RFC 7766 décrivait la norme technique, et s'imposait donc aux programmeur·ses, ce RFC 9210 est plus opérationnel et concerne donc ceux et celles qui installent et configurent les serveurs DNS et les réseaux qui y mènent.

Les messages DNS sont historiquement surtout transportés sur UDP. Mais le DNS a toujours permis TCP et, de nos jours, il est fréquemment utilisé notamment en conjonction avec TLS (cf. RFC 7858). Il est d'autant plus important que TCP marche bien qu'il est parfois nécessaire pour certains usages, par exemple pour des enregistrements de grande taille (ce qui arrive souvent avec DNSSEC). Une mise en œuvre du DNS doit donc inclure TCP, comme clarifié par le RFC 7766 (les RFC précédents n'étaient pas sans ambiguité). Mais cela ne signifiait pas forcément que les serveurs DNS avaient activé TCP ou que celui-ci marchait bien (par exemple, une stupide middlebox pouvait avoir bloqué TCP). Ce nouveau RFC 9210 ajoute donc que l'obligation d'avoir TCP s'applique aussi aux serveurs effectifs, pas juste au logiciel.

DNS sur TCP a une histoire compliquée (section 2 du RFC). Franchement, le fait que le DNS marche sur TCP aussi bien que sur UDP n'a pas toujours été clairement formulé, même dans les RFC. Et, en dehors de l'IETF, beaucoup de gens ont raconté n'importe quoi dans des articles et des documentations, par exemple que TCP n'était utile qu'aux transferts de zone, ceux normalisés dans le RFC 5936. Cette légende a été propagée par certains auteurs (comme Cheswick et Bellovin dans leur livre Firewalls and Internet Security: Repelling the Wily Hacker) ou par des gens qui ne mesuraient pas les limites de leurs compétences DNS comme Dan Bernstein. Pourtant, TCP a toujours été nécessaire, par exemple pour transporter des données des grande taille (que DNSSEC a rendu bien plus fréquentes). Le RFC 1123, en 1989, insistait déjà sur ce rôle. Certes, EDNS (RFC 6891) permettait déjà des données de taille supérieure aux 512 octets de la norme originelle. Mais il ne dispense pas de TCP. Par exemple, des données de taille supérieure à 1 460 octets (le maximum qui peut tenir avec le MTU typique) seront fragmentées et les fragments, hélas, ne passent pas partout sur l'Internet. (Cf. le « DNS Flag Day » de 2020 et lire « DNS XL » et « Dealing with IPv6 fragmentation in the DNS ».) Et la fragmentation pose également des problèmes de sécurité, voir par exemple « Fragmentation Considered Poisonous ».

Bon, en pratique, la part de TCP reste faible mais elle augmente. Mais, trop souvent, le serveur ne répond pas en TCP (ou bien ce protocole est bloqué par le réseau), ce qui entraine des tas de problèmes, voir par exemple le récit dans « DNS Anomalies and Their Impacts on DNS Cache Servers ».

Notons enfin que TCP a toujours permis que plusieurs requêtes se succèdent sur une seule connexion TCP, même si les premières normes n'étaient pas aussi claires qu'il aurait fallu. L'ordre des réponses n'est pas forcément préservé (cf. RFC 5966), ce qui évite aux requêtes rapides d'attendre le résultat des lentes. Et un client peut envoyer plusieurs requêtes sans attendre les réponses (RFC 7766). Par contre, la perte d'un paquet va entrainer un ralentissement de toute la connexion, et donc des autres requêtes (QUIC résout ce problème et il y a un projet de DNS sur QUIC).

Vous pouvez tester qu'un serveur DNS accepte de faire passer plusieurs requêtes sur une même connexion TCP avec dig et son option +keepopen (qui n'est pas activée par défaut). Ici, on demande à ns4.bortzmeyer.org les adresses IP de www.bortzmeyer.org et mail.bortzmeyer.org :

% dig +keepopen +tcp @ns4.bortzmeyer.org www.bortzmeyer.org AAAA mail.bortzmeyer.org AAAA
  

Vous pouvez vérifier avec tcpdump qu'il n'y a bien eu qu'une seule connexion TCP, ce qui ne serait pas le cas sans +keepopen.

Les exigences opérationnelles pour le DNS sur TCP figurent dans la section 3 du RFC. Il est désormais obligatoire non seulement d'avoir la possibilité de TCP dans le code mais en outre que cela soit activé partout. (En termes du RFC 2119, on est passé de SHOULD à MUST.)

La section 4 discute de certaines considérations opérationnelles que cela pourrait poser. D'abord, certains serveurs ne sont pas joignables en TCP. C'était déjà mal avant notre RFC mais c'est encore pire maintenant. Mais cela arrive (ce n'est pas forcément la faute du serveur, cela peut être dû à une middlebox boguée sur le trajet). Les clients doivent donc être préparés à ce que TCP échoue (de toute façon, sur l'Internet, tout peut toujours échouer). D'autre part, diverses attaques par déni de service sont possibles, aussi bien contre le serveur (SYN flooding, contre lequel la meilleure protection est l'utilisation de cookies, cf. RFC 4987), que contre des machines tierces, le serveur étant utilisé comme réflecteur.

Aussi bien le client DNS que le serveur n'ont pas des ressources illimitées et doivent donc gérer les connexions TCP. Dit plus brutalement, il faut être prêt à couper les connexions qui semblent inutilisées. Bien sûr, il faut aussi laisser les connexions ouvertes suffisamment longtemps pour amortir leur création sur le plus grand nombre de requêtes possibles, mais il y a des limites à tout. (Le RFC suggère une durée maximale dans les dizaines de secondes, pouvant être abaissée à quelques secondes pour les serveurs très chargés.)

TCP est particulièrement intéressant pour le DNS quand TLS est inséré (RFC 7858). Mais il va alors consommer davantage de temps de CPU et, dans certains cas, l'établissement de connexion sera plus lent.

Le RFC donne quelques conseils quantitatifs (à ne pas appliquer aveuglément, bien sûr). Un maximum de 150 connexions TCP simultanées semble raisonnable pour un serveur ordinaire, avec un maximum de 25 par adresse IP source. Un délai de garde après inactivité de 10 secondes est suggéré. Par contre, le RFC ne propose pas de valeur maximale au nombre de requêtes par connexion TCP, ni de durée maximale à une connexion.

Les fanas des questions de sécurité noteront que les systèmes de journalisation et de surveillance peuvent ne pas être adaptés à TCP. Par exemple, un méchant pourrait mettre la requête DNS dans plusieurs segments TCP et donc plusieurs paquets IP. Un système de surveillance qui considérerait qu'une requête = un paquet serait alors aveugle. Notez qu'un logiciel comme dnscap a pensé à cela, et réassemble les paquets. Mais il est sans doute préférable, de nos jours, de se brancher sur le serveur, par exemple avec dnstap, notamment pour éviter de faire le réassemblage deux fois. (Ceux qui lisent mes articles depuis longtemps savent que je donnais autrefois le conseil inverse. Mais le déploiement de plus en plus important de TCP et surtout de TLS impose de changer de tactique.)


Téléchargez le RFC 9210


L'article seul

RFC 9209: The Proxy-Status HTTP Response Header Field

Date de publication du RFC : Juin 2022
Auteur(s) du RFC : M. Nottingham (Fastly), P. Sikora (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 14 juin 2022


Le protocole HTTP, qui est à la base du Web, n'est pas forcément de bout en bout, entre client et serveur. Il y a souvent passage par un relais et ce relais a parfois des choses à signaler au client HTTP, notamment en cas d'erreur. Ce RFC spécifie le champ d'en-tête Proxy-Status pour cela.

La norme HTTP, le RFC 9110 décrit ces relais, ces intermédiaires, dans sa section 3.7. On en trouve fréquemment sur le Web. Il y a depuis longtemps des codes d'erreur pour eux, comme 502 si le serveur d'origine répond mal et 504 pour les cas où il ne répond pas du tout. Mais ce n'est pas forcément assez précis, d'où le nouveau champ dans l'en-tête (ou dans le pied). Il utilise la syntaxe des champs structurés du RFC 8941. Voici un exemple :


Proxy-Status: proxy.example.net; error="http_protocol_error"; details="Malformed response header: space before colon",
  "Example CDN"

  

Cet exemple se lit ainsi : le premier (en partant du serveur d'origine) s'identifie comme proxy.example.net et il signale que le serveur d'origine n'avait pas bien lu le RFC 9112. Puis la réponse est passée par un autre intermédiaire, qui s'identifie comme "Example CDN" (l'identificateur n'est pas forcément un nom de domaine), et n'a rien de particulier à raconter. Le champ Proxy-Status est désormais dans le registres des champs d'en-tête (ou de pied).

Vous avez vu dans l'exemple ci-dessus le paramètre error. Il peut s'utiliser, par exemple, avec le code de retour 504 :

HTTP/1.1 504 Gateway Timeout
Proxy-Status: foobar.example.net; error=connection_timeout
  

Ici, le relais foobar.example.net n'a eu aucune réponse du serveur d'origine (ou, plus rigoureusement, du serveur qu'il essayait de contacter, qui peut être un autre intermédiaire).

Mais il existe d'autres paramètres possibles, comme :

  • next-hop : nom ou adresse du serveur contacté, par exemple Proxy-Status: cdn.example.org; next-hop=backend.example.org:8001.
  • next-protocol : protocole (ALPN, RFC 7301) utilisé avec le serveur, par exemple Proxy-Status: "proxy.example.org"; next-protocol=h2 pour du HTTP/2 (RFC 9113).
  • received-status : le code de retour du serveur, comme dans Proxy-Status: ExampleCDN; received-status=200, pour un cas où tout s'est bien passé.

Et on peut définir de nouveaux paramètres par la procédure d'examen par un expert (RFC 8126).

Le paramètre error prend comme valeur un type d'erreur. Il en existe plusieurs, chacun avec un code de retour recommandé dont (je ne les indique pas tous, ils sont très nombreux !) :

  • dns_timeout (pour le code de retour 504) et dns_error (code 502) : c'est la faute du DNS. Le second type permet en outre d'indiquer le paramètre rcode (code de retour DNS, comme NXDOMAIN) et le paramètre info-code, le code étendu du RFC 8914.
  • connection_timeout ou connection_refused.
  • destination_ip_prohibited : le pare-feu ne veut pas.
  • tls_protocol_error : là, c'est TLS qui ne veut pas.
  • tls_certificate_error : certificat du serveur problématique, par exemple expiré.
  • http_protocol_error : la réponse du serveur n'était pas du bon HTTP.
  • proxy_internal_error : le relais a un problème interne.

Là aussi, on peut enregistrer de nouveaux types avec la procédure d'examen par un expert du RFC 8126.

La section 4 du RFC détaille les questions de sécurité. Comme toute information, Proxy-Status peut aider un attaquant, par exemple en lui donnant des idées sur comment joindre directement un intermédiaire. Pour cette raison, les logiciels doivent fournir un moyen de contrôler l'ajout (ou pas) de Proxy-Status, qu'on peut aussi n'inclure que dans certains cas. Notez aussi qu'un intermédiaire peut mentir (ou se tromper) et que le Proxy-Status ne vaut donc que ce que vaut l'intermédiaire.

Je n'ai pas de liste des logiciels qui mettent en œuvre ce champ Proxy-Status.


Téléchargez le RFC 9209


L'article seul

RFC des différentes séries : 0  1000  2000  3000  4000  5000  6000  7000  8000  9000