Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

Ce blog n'a d'autre prétention que de me permettre de mettre à la disposition de tous des petits textes que j'écris. On y parle surtout d'informatique mais d'autres sujets apparaissent parfois.


Mon exposé sur la neutralité du code à Touraine Tech

Première rédaction de cet article le 23 janvier 2023


Le 20 janvier de cette année, à la conférence informatique Touraine Tech, j'ai fait un exposé sur la neutralité du code. Lorsque le ou la développeureuse dit « je ne fais pas de politique, ce programme pourra être utilisé pour différents buts », est-ce possible ?

Il y avait à cet évènement de nombreuses autres conférences intéressantes(sans compter les fouées aux rillettes) :

  • L'introduction de Ploum, très drôle et très vivante,
  • la conférence d'Amélie Abdallah « Féminisation dans la tech : la communication, ou l'échec de tout un secteur » sur le sexisme dans l'informatique et comment le combattre (supports ici),
  • celle de Jimmy Kasprzak, « Développer et enseigner, une bonne idée ? » sur l'importance et la difficulté de l'enseignement,
  • la conférence dessinée (très bons dessins) de Victor Lambret sur les méthodes de développement logiciel, où comment des études scientifiques montrent que les conseils de bon sens des consultants, même appuyés sur un sigle à la mode (comme TDD) ne produisent pas de meilleur code,
  • celle de Cécile Freyd-Foucault et Iris Naudin le jeudi soir, sur des thèmes classiques (accessibilité et fracture numérique) mais très bien traités (à faire suivre par les gens qui ignorent encore le problème) ; les références utilisées pour leur exposé sont en ligne, j'ai beaucoup aimé l'article d'Anne-Sophie Tranchet sur les formulaires odieux,
  • l'amusant « Développer publiquement un site en milieu hostile : retour d'expérience », d'Alexis Lozano, où il détaille les étapes sucessives d'un petit projet Web avec contenu fourni par les utilisateurices, et les problèmes successifs dûs aux abrutis, exposé très bien fait, très intéressant et concret,
  • comme aujourd'hui on fait de l'IA partout, la conférence « Si on aidait le capitaine Némo à classifier les monstres marins ? » de Eléa Petton et Stéphane Philippart, est recommandée, un tutoriel nous guidant pas à pas dans la construction d'une application de reconnaissance de sons, très pédagogique. Plusieurs démonstrations concrètes avec Python et Jupyter (et les bibliothèques librosa, pandas, sklearn, tensorflow, etc).
  • et enfin la conférence très détaillée de Fabien Trégan et Olivier Poncet sur la représentation des nombres dans les ordinateurs (même si vous savez déjà que (1/3)*3 ne fait pas 1, vous y apprendrez des choses), avec des supports écrits à la main sur un fond évoquant un cahier d'écolier.

Les vidéos de ces différentes conférences devraient être en ligne dans les prochains jours (sans doute sur cette chaine).

Un exposé d'Olivier Mertens portait sur le générateur d'images DALL-E (fait par la même société que ChatGPT). J'ai donc demandé à DALL-E « Une conférence à TouraineTech dans le style de François Boucher [puisqu'il y avait une exposition de ce dernier au Musée des Beaux-Arts] » et le résultat est… pas terrible : touraine-tech-2023-dall-e.jpg.

Et merci mille fois aux organisateurices (une telle conférence crée beaucoup de travail), et à Florian Roulet pour m'avoir invité à y prendre la parole.

Ploum et moi dédicaçant nos livres : touraine-tech-2023-dedicaces.jpg


L'article seul

ChatGPT remplacera t-il les programmeuses et programmeurs ?

Première rédaction de cet article le 3 janvier 2023


Si Ada Lovelace était la première programmeuse, les programmeuses et programmeurs actuel·les sont-ielles les dernièr·es ? Un programme « intelligent » comme ChatGPT va-t-il les condamner à la reconversion ou au chômage ? Je continue ici mon exploration de ChatGPT.

ChatGPT a beaucoup été utilisé pour la programmation. Voici une session où je lui demande des programmes (ce sera la seule image, dans le reste de l'article, je copierai le texte).

Exemple d'utilisation :chatgpt-http-elixir.png

Le programme est écrit en Elixir car mes questions précédentes portaient sur ce langage et ChatGPT mémorise les conversations (et il s'en sert, est-on averti, pour s'améliorer donc ne lui donnez pas d'informations confidentielles). L'utilisation d'Elixir montre que ChatGPT connait beaucoup de choses, y compris des langages assez rarement utilisés. C'est logique : pour le quantitatif (la mémorisation et la restitution d'énormes quantités d'informations), les ordinateurs sont certainement meilleurs que les humains. D'autre part, celles et ceux qui ont lu mon précédent article noteront que j'ai maintenant un compte ChatGPT, ayant trouvé un numéro de téléphone sacrifiable.

Beaucoup de gens ont été impressionnés par les qualités de ChatGPT et, voyant la facilité avec laquelle il génère des programmes (et les explique !), se sont dit que les programmeuses et les programmeurs allaient disparaitre. Un tel discours fait rêver bien des patrons, qui apprécieraient de remplacer des développeurs et développeuses payés chers par une IA docile et obéissante. Ce discours est donc très ancien dans l'informatique, Cobol avait déjà été promu avec cet argument. Et c'était également le cas de Fortran (« FORTRAN should virtually eliminate coding and debugging »). Même si ces affirmations semblent techniquement ridicules aujourd'hui, à l'époque, le progrès par rapport au langage d'assemblage était tel que cela ne semblait pas absurde. Puis on a entendu à nouveau ce raisonnement du temps des L4G puis récemment du nocode. Le fait que les espérances des patrons aient été déçues à chaque fois ne les empêche pas de garder espoir.

Voyons donc ce que sait faire ChatGPT. D'abord, on l'a dit, il connait beaucoup de choses. Je demande « Je dois trier une liste de chaînes de caractères [un exercice banal de débutant]. Comment faire ? » et ChatGPT répond « Pour trier une liste de chaînes de caractères en Elixir, vous pouvez utiliser la fonction Enum.sort/1. Cette fonction prend en entrée une liste et renvoie une liste triée de ses éléments. Voici un exemple de code qui trie une liste de chaînes de caractères en ordre croissant : »

strings = ["c", "b", "a", "d"]
sorted_strings = Enum.sort(strings)
IO.inspect(sorted_strings) # ["a", "b", "c", "d"]

Il connait la bibliothèque standard d'Elixir (où il y a bien un Enum.sort) et aussi des idiosyncrasies d'Elixir comme le fait de nommer une fonction en terminant par son arité. Et il ajoute spontanément « Si vous voulez trier la liste en ordre décroissant, vous pouvez utiliser la fonction Enum.sort/2 et lui fournir une fonction de comparaison qui renvoie :gt si le premier argument est plus grand que le second, :lt si le premier argument est plus petit que le second, et :eq s'ils sont égaux. Voici un exemple de code qui trie une liste de chaînes de caractères en ordre décroissant : »

strings = ["c", "b", "a", "d"]
sorted_strings = Enum.sort(strings, fn x, y -> if x > y do :gt else if x < y do :lt else :eq end end)
IO.inspect(sorted_strings) # ["d", "c", "b", "a"]

(Les :gt et :lt sont appelés atomes et sont une des particularités d'Elixir. Je vous ai dit qu'il « connaissait » bien le langage.)

Mais ce second code pose déjà des problèmes. D'abord, il ne compile même pas (il manque un end). Mais, surtout, ChatGPT n'a pas bien lu la documentation : la fonction passée en argument à sort est censée renvoyer un booléen, pas ces atomes. On voit là une première chose à garder en mémoire : on ne peut pas en général utiliser les programmes produits par ChatGPT tels quels, il faut les vérifier, et cette vérification doit être faite par une personne compétente en programmation. Et il n'est pas évident que l'entièreté du processus soit plus rapide et plus sûr que si cette personne compétente avait tout fait elle-même. (L'erreur de syntaxe est la moins grave, car elle est détectée à la compilation. Le pire serait une erreur qui ne planterait pas mais donnerait un résultat faux.)

Ceci dit, ChatGPT apprend et il est interactif. Voyons cela sur un autre exemple : « Comment décomposer un entier de grande taille en ses facteurs premiers ? ». ChatGPT répond « Pour décomposer un entier en ses facteurs premiers en Elixir, vous pouvez utiliser une approche récursive. Voici comment vous pouvez implémenter cette approche : »

defmodule PrimeFactors do
  def factors(n, divisor \\ 2) when divisor > n do
    [n]
  end
  def factors(n, divisor \\ 2) do
    if rem(n, divisor) == 0 do
      [divisor | factors(n / divisor, divisor)]
    else
      factors(n, divisor + 1)
    end
  end
end    

Cette fois, il n'y a pas d'erreur de syntaxe, mais cela ne compile quand même pas, en raison d'une erreur sémantique. Et il y a deux autres bogues (trouver l'une d'elles nécessite de connaitre Elixir). Mais comme ChatGPT est interactif, je lui explique : « Attention, on ne peut pas avoir deux définitions avec des valeurs par défaut. Il faut réécrire ton code. » Il me répond que j'ai raison (d'autres personnes ont vu ChatGPT nier des problèmes) et « Vous avez raison, il n'est pas possible d'avoir deux définitions avec des valeurs par défaut pour le même nom de fonction en Elixir. Pour résoudre ce problème, vous pouvez utiliser une seule définition de fonction avec une valeur par défaut pour le diviseur, et ajouter une condition pour gérer le cas où le diviseur est supérieur à n. Voici comment vous pouvez réécrire le code : »

    
defmodule PrimeFactors do
  def factors(n, divisor \\ 2) when divisor > n do
    [n]
  end
  def factors(n, divisor \\ 2) do
    if divisor > n do
      [n]
    else
      if rem(n, divisor) == 0 do
        [divisor | factors(n / divisor, divisor)]
      else
        factors(n, divisor + 1)
      end
    end
  end
end

  

Et il ajoute spontanément « Je vous recommande également de changer le nom de la fonction factors/2 en factors/1 et de changer la condition de la clause de cas factors/1 pour qu'elle vérifie si n est inférieur ou égal à 1. Voici comment le code final devrait ressembler [sic] : »


defmodule PrimeFactors do
  def factors(n, divisor \\ 2) when n <= 1 do
    []
  end
  def factors(n, divisor \\ 2) do
    if rem(n, divisor) == 0 do
      [divisor | factors(n / divisor, divisor)]
    else
      factors(n, divisor + 1)
    end
  end
end

  

C'est amusant, le code réécrit a toujours la même bogue sur la définition de fonctions avec des valeurs par défaut. Par contre, ChatGPT a « réfléchi » et a corrigé une des autres bogues (qui faisait que le nombre 1 était listé comme facteur premier). Bon, tant pis, je corrige manuellement et je lui signale la troisième bogue (celle qui nécessite de connaitre Elixir) : « Et attention, le / fait une division de réels, il faudrait utiliser div. ». Il le reconnait : « Vous avez raison, la fonction / en Elixir effectue une division de réels et non une division entière. Pour effectuer une division entière, vous pouvez utiliser la fonction div/2. Voici comment vous pouvez utiliser la fonction div/2 dans la fonction factors/1 : »


defmodule PrimeFactors do
  def factors(n, divisor \\ 2) when n <= 1 do
    []
  end
  def factors(n, divisor \\ 2) do
    if rem(n, divisor) == 0 do
      [divisor | factors(div(n, divisor), divisor)]
    else
      factors(n, divisor + 1)
    end
  end
end

  

À noter que ChatGPT avait montré qu'il connaissait l'éco-système Elixir, en expliquant comment tester avec l'interpréteur iex :

iex> PrimeFactors.factors(12)
[2, 2, 3]
iex> PrimeFactors.factors(2310)
[2, 3, 5, 7, 11]
iex> PrimeFactors.factors(7829)
[7829]
  

Prenons un autre exemple qui illustre la question du cahier des charges. En programmation dans le monde réel, c'est souvent la principale difficulté ; le·a programmeur·se passe souvent davantage de temps à comprendre un cahier des charges incomplet, contradictoire et irréaliste qu'à programmer. Commençons simplement : « Écris un programme en Python qui donne la liste des nombres allant jusqu'à N. ». ChatGPT répond :

# Demande à l'utilisateur de saisir un entier N
n = int(input("Entrez un entier N : "))

# Initialise une liste vide
l = []

# Pour chaque entier i allant de 1 à N
for i in range(1, n+1):
  # Ajoute i à la liste
  l.append(i)

# Affiche la liste
print(l)

Et explique « Ce programme demande à l'utilisateur de saisir un entier N, initialise une liste vide, puis utilise une boucle for pour ajouter chaque entier allant de 1 à N à la liste. Enfin, il affiche la liste. ». Modifions le cahier des charges (ce qui est courant) : «  Il faut que cela marche avec des entiers relatifs. » ChatGPT ne se démonte pas pour si peu « Pas de problème, voici une version modifiée du programme qui prend en compte les entiers relatifs : »

  
# Demande à l'utilisateur de saisir un entier N
n = int(input("Entrez un entier N : "))

# Initialise une liste vide
l = []

# Si N est positif
if n > 0:
  # Pour chaque entier i allant de 1 à N
  for i in range(1, n+1):
    # Ajoute i à la liste
    l.append(i)

# Si N est négatif
elif n < 0:
  # Pour chaque entier i allant de N à -1 (inclus)
  for i in range(n, 0):
    # Ajoute i à la liste
    l.append(i)

# Si N est égal à 0
else:
  # Ajoute 0 à la liste
  l.append(0)

# Affiche la liste
print(l)

Et explique « Cette version vérifie si N est positif, négatif ou égal à 0 et utilise une boucle for appropriée pour ajouter les entiers à la liste. Si N est égal à 0, 0 est simplement ajouté à la liste. » On voit que ChatGPT est parfaitement capable de faire des exercices de programmation pour grands débutants, par exemple au lycée. Mais comme je suis méchant, je lui demande ensuite un programme qui marche avec les nombres réels, ce qui est impossible. Il me fait alors une variante qui énumère, mais avec un pas (qu'il a choisi arbitrairement) de 0,1. Dois-je le considérer comme très bête ou comme très intelligent d'avoir interprété de manière créative un cahier des charges absurde ? En tout cas, cet exemple montre à quoi ressemble souvent le travail de programmation : il faut de nombreuses itérations avant d'avoir une idée claire de ce qui est demandé et un programme comme ChatGPT n'est pas forcément le mieux placé pour cela. (Mes lecteurices qui connaissent la programmation auront déjà fait remarquer in petto que si on ne peut pas énumérer les réels - qui sont en nombre infini dans tout intervalle, quelle que soit sa taille, on pourrait par contre énumérer les nombres en virgule flottante représentables sur la machine. Mais ChatGPT n'y a pas pensé.)

Mais, bref, on est encore loin d'un système qui pourrait permettre de se passer complètement des développeurs et développeuses. Il peut aider, c'est certain, faisant gagner du temps lorsqu'on a oublié comment faire une requête HTTP dans tel ou tel langage de programmation, il peut servir d'« auto-complétion améliorée » mais cela ne permet pas de lui confier tout le travail. Alors, certes, il s'agit d'Elixir, mais est-ce que cela marcherait mieux avec des langages de programmation plus répandus ? J'en doute. Mémoriser une grande quantité d'informations et pouvoir en ressortir à volonté est justement le point fort des ordinateurs. Contrairement à un humain, apprendre un langage de plus ne nécessite guère d'effort. Les différents messages lus sur les réseaux sociaux semblent indiquer que des langages bien plus communs qu'Elixir ont le même problème (pareil pour la simple algèbre).

Parmi les autres faiblesses de ChatGPT qui font obstacle à une utilisation en totale autonomie, notons aussi :

  • ChatGPT n'est pas réflexif, il ne s'analyse pas lui-même et n'est pas capable de donner un degré de certitude sur ses « solutions ». Il affirme souvent des choses fausses avec le même niveau de confiance que les choses les plus indiscutables. Et il invente souvent des choses qui n'existent pas (articles scientifiques, options de logiciel, etc).
  • Certains promoteurs de ChatGPT disent ne pas s'inquiéter de la mauvaise qualité (parfois) du code en considérant que, du moment que « ça marche », c'est suffisant. Mais cet argument ne vaut pas pour la sécurité. Si un programmme a une faille de sécurité, il va marcher, mais être néanmoins dangereux.

En conclusion, oui, ChatGPT et les logiciels similaires vont changer la programmation (comme Cobol et Fortran l'avaient fait) mais croire qu'ils vont permettre de se passer de programmeur-ses est illusoire. Les développeur·euses qui produisent du code de basse qualité à la chaîne ont du souci à se faire pour leur avenir professionnel mais cela ne concerne pas celles et ceux qui font du code créatif, difficile, novateur, ou simplement bien adapté à leur problème. ChatGPT est très fort pour des tests simplistes, comme ceux souvent faits à l'embauche mais pour de vrais programmes ? C'est un point souvent oublié par les personnes qui ne connaissent pas la programmation : écrire un petit script qui fait une tâche simple n'est pas la même chose que de programmer Apache, Louvois, ou un logiciel de calcul scientifique comme Blast.

Quelques réflexions et éléments sur ce sujet :


L'article seul

Un modeste avis sur ChatGPT

Première rédaction de cet article le 14 décembre 2022


Tout le monde aujourd'hui utilise ChatGPT et envoie sur les réseaux sociaux les résultats les plus amusants ou les plus spectaculaires. La plupart des retours sont admiratifs devant les performances de ce système d'« Intelligence Artificielle » (IA) pour répondre à des questions et générer des textes. Mais il faut nuancer un peu.

Tout d'abord, un sérieux avertissement : je n'ai pas essayé ChatGPT moi-même, en raison de leur demande excessive de données personnelles (il faut indiquer une adresse de courrier et un numéro de téléphone, et les deux sont vérifiées par envoi d'un code à retourner ensuite ; on ne peut donc pas tricher). J'ai seulement lu les résultats de ChatGPT tels que publiés par ses utilisateurs. Si vous pensez que cela rend cet article sans valeur, arrêtez votre lecture tout de suite et allez faire autre chose, par exemple regarder les belles aquarelles d'Aemarielle.

Maintenant, revenons à ChatGPT. On lui pose des questions et il répond. Le résultat est souvent étonnant, par exemple lorsqu'on lui demande des textes « écrits dans le style de [telle personne] » ou lorsqu'on l'interroge sur des questions techniques complexes. Cela illustre de manière publique les progrès importants des techniques connues sous le nom commercial d'IA (Intelligence Artificielle), ainsi que la quantité vraiment colossale de données que ChatGPT a lues et assimilées. Il est par exemple à noter que ChatGPT écrit des textes bien meilleurs que ce que font beaucoup d'humains, y compris dans un environnement professionnel (« écris un communiqué de presse se félicitant de l'augmentation de X % du chiffre d'affaires de la société Machin » donnera un résultat indistinguable du « vrai »). Notamment, il ne fait aucune faute d'orthographe ou de grammaire.

Est-ce que cela signifie qu'on peut réellement parler d'intelligence et que les humains sont désormais inutiles ? Pas si vite. D'abord, l'intelligence, ce n'est pas de savoir exécuter une tâche, c'est de savoir quelle tâche exécuter. De ce point de vue, ChatGPT est loin de l'intelligence. Plusieurs personnes ont pu constater qu'on pouvait lui demander des textes contradictoires (« explique pourquoi il est important d'augmenter les impôts » puis « explique pourquoi il faut baisser les impôts », et ChatGPT s'exécutera).

Ensuite, ce que révèle ChatGPT, ce ne sont pas tellement les progrès de l'IA que le creux et l'absence de contenu de beaucoup de textes produits par des humains. ChatGPT sait faire des devoirs d'étudiants de première année, écrire des communiqués de presse, du reporting et produire les discours des ministres. Grâce à ce système, on voit bien que ces textes n'ont pas tellement de fond et ne nécessitent pas beaucoup d'intelligence, uniquement la lecture et le traitement d'une grande quantité d'informations, tâche où les humains sont certainement inférieurs aux ordinateurs. ChatGPT ne remplacera donc pas les humains mais lui ou ses successeurs pourront prendre en charge des tâches qui étaient considérées à tort comme nécessitant de l'intelligence. Comme le note Stéphane Mouton, ChatGPT est toujours « correct mais superficiel ».

Cela va certainement « disrupter » certains secteurs, comme celui des rédacteurs sous-payés qui écrivent vite et mal. Pour prendre un autre exemple, j'ai vu des étudiants de master produire des notes qui ne valaient pas ce que fait ChatGPT. L'enseignement devra donc s'adapter. Mais cela poussera à réfléchir à ce que nous voulons que les humains fassent. Écrire des synthèses fades et sans originalité ou bien travailler de manière plus créative ?

Mais, diront certains, ChatGPT et l'IA en général vont continuer à progresser. Les limites actuelles seront forcément dépassées. Eh bien non, ou plus exactement, c'est plus compliqué que cela. La marche du progrès technique peut faire croire que le progrès est forcément linéaire, chaque année marquant une amélioration technique. Des observations comme la loi de Moore vont en ce sens. Mais ce n'est pas une règle générale du progrès. Il y a également des techniques qui stagnent, ou qui ne progressent que par bonds imprévisibles. L'IA en est un bon exemple : depuis ses débuts (qui sont à peu près ceux de l'informatique), elle alterne des bonds spectaculaires avec de longues périodes de repos, le bond spectaculaire ayant été suivi d'une constatation qu'on n'arrivait pas à l'améliorer. Peut-être que ChatGPT va progresser, ou peut-être qu'il ne dépassera pas son stade actuel avant longtemps, mais on ne peut pas affirmer qu'il fera forcément mieux dans le futur.

Et sinon, non, petits coquins, cet article n'a pas été écrit par ChatGPT, Bruce Schneier a fait la blague avant moi (et je suis d'accord avec la plupart des commentaires à son article ; l'article est sans erreur mais enfonce des portes ouvertes et ne fait preuve d'aucune réflexion).


L'article seul

RFC 8521: Registration Data Access Protocol (RDAP) Object Tagging

Date de publication du RFC : Novembre 2018
Auteur(s) du RFC : S. Hollenbeck (Verisign Labs), A. Newton (ARIN)
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 23 novembre 2022


Contrairement à son lointain prédécesseur whois, le protocole d'accès aux informations des registres RDAP a un mécanisme standard de découverte du serveur faisant autorité. Ce mécanisme est prévu pour des données organisées de manière arborescente comme les noms de domaine ou les adresses IP. Pour des objets qui n'ont pas cette organisation, comment faire ? Ce RFC fournit une solution pour des objets quelconques, s'ils obéissent à une convention de nommage simple. C'est le cas des handles, ces identificateurs d'entités (titulaires de noms de domaine, contacts pour un préfixe IP, etc) ; s'ils sont de la forme QUELQUECHOSE-REGISTRE, on pourra trouver le serveur responsable.

Le mécanisme habituel de découverte du serveur est normalisé dans le RFC 9224. En gros, l'IANA garde un registre des serveurs, indexé par un nom de domaine ou par un préfixe IP. Ce registre est au format JSON. Un client RDAP est juste censé télécharger ce fichier, trouver le serveur faisant autorité, puis le contacter. Pour pouvoir faire la même chose avec des objets non structurés, il faut leur imposer une convention de nommage. Si on interroge avec RDAP (ou whois) l'adresse IP 88.198.21.14, on voit que son contact technique est HOAC1-RIPE. Cette convention de nommage est « d'abord un identifiant unique au sein du registre, puis un tiret, puis le nom du registre, ici le RIPE-NCC ». L'IANA a juste à garder un registre de ces registres qui nous dira, par exemple, que RIPE a comme serveur RDAP https://rdap.db.ripe.net/. On peut ensuite l'interroger en RDAP :

%  curl https://rdap.db.ripe.net/entity/HOAC1-RIPE
   "vcardArray" : [ "vcard", [ [ "version", { }, "text", "4.0" ], [ "fn", { }, "text", "Hetzner Online GmbH - Contact Role" ], [ "kind", { }, "text", "group" ], [ "adr", {
    "label" : "Hetzner Online GmbH\nIndustriestrasse 25\nD-91710 Gunzenhausen\nGermany"
  }, "text", [ "", "", "", "", "", "", "" ] ], [ "tel", {
    "type" : "voice"
  }, "text", "+49 9831 505-0" ], [ "tel", {
...
  

Petit piège, la partie du début peut elle-même comporter des tirets donc lorsqu'on coupe en deux un handle, il faut le faire sur le dernier tiret (section 2 du RFC). Pourquoi le tiret, d'ailleurs ? Parce qu'il est couramment utilisé comme séparateur, et parce que son utilisation dans un URL ne pose pas de problème particulier (RDAP utilise HTTPS et donc des URL).

Le registre des serveurs RDAP est géré à l'IANA comporte un tableau JSON des services, chaque entrée étant composée de trois tableaux, la liste des adresses de courrier du responsable, la liste des suffixes (en général, un seul par registre) et la liste des serveurs RDAP. Par exemple, pour l'ARIN, on aura :

[
      [
        "andy@arin.net"
      ],
      [
        "ARIN"
      ],
      [
        "https://rdap.arin.net/registry/",
        "http://rdap.arin.net/registry/"
      ]
]  
  

Les entrées dans le registre sont ajoutées selon la politique « Premier Arrivé, Premier Servi » du RFC 8126.

Comme ce RFC décrit une extension à RDAP, le serveur RDAP doit ajouter à sa réponse, dans l'objet rdapConformance, rdap_objectTag_level_0. (Apparemment, personne ne le fait.)

Vous voulez du code ? La bibliothèque ianardap.py, qui est disponible en ligne, peut récupérer la base et l'analyser. Cela permet ensuite de faire des requêtes pour des objets :

% ./dump-object.py F11598-FRNIC  | jq .
...
       [
          "",
          "",
          "Association Framasoft c/o Locaux Motiv",
          "Lyon",
          "",
          "69007",
          "FR"
        ]
...
  

Le programme dump-object.py étant simplement :

#!/usr/bin/env python3

import ianardap

import requests

import sys

rdap = ianardap.IanaRDAPDatabase(category="objects")
for object in sys.argv[1:]:
    url = rdap.find(object)
    if url is None:
        print("No RDAP server for %s" % object, file=sys.stderr)
        continue
    response = requests.get("%sentity/%s" % (url[0], object))
    print(response.text)
  

Téléchargez le RFC 8521


L'article seul

Capitole du Libre 2022

Première rédaction de cet article le 21 novembre 2022


Les 19 et 20 novembre 2022, c'était Capitole du Libre à Toulouse, un rassemblement libriste important, qui reprenait après l'interruption Covid-19. Il y avait beaucoup de rencontres, d'ateliers et de conférences intéressantes. (En tout, 118 orateurs, 97 conférences, 16 ateliers et 39 stands au village associatif.)

J'ai fait deux exposés. Le premier, un peu plus politique, portait sur une question de programmation. Maintenant que tout est chiffré, comment déboguer les applications réseau alors qu'on ne peut plus utiliser Wireshark comme avant ? Il existe plusieurs solutions, de la lutte pour limiter le chiffrement, comme le font encore certains réactionnaires, à la coopération de l'application, qui est l'avenir. Voici la version en PDF, et le source en LaTeX/Beamer.

Le second exposé tirait les leçons du développement de Drink, un serveur DNS faisant autorité voué aux réponses dynamiquement générées. Voici la version en PDF, et le source en LaTeX/Beamer. Le code de Drink est disponible en ligne.

J'ai aussi participé à une table ronde sur le thème de la « souveraineté numérique », avec Gaël Duval, Philippe Latombe, et Amandine Le Pape, table ronde animée par Étienne Gonnu.

J'ai également participé à une séance dédicace de mon livre « Cyberstructure », qui avait été annoncé pour la première fois publiquement au Capitole du Libre il y a quatre ans. La séance était organisée par la librairie « Les petits ruisseaux ». J'étais à côté de David Revoy, l'auteur de Pepper et Carrot, qui faisait de superbes dessins.

Le programme de Capitole du Libre était très riche. J'ai beaucoup aimé « Introduction à Rust embarqué » de Sylvain Wallez, avec des belles démonstrations (l'inévitable diode clignotante) de ce langage en direct, sur un ATtiny85 (0,5 ko de RAM, 8 ko de flash) et un plus gros microcontrôleur ESP32. Également très technique, la descente en profondeur dans les entrailles des composants Ethernet « Plongée au coeur d'Ethernet, et son support dans Linux », de Maxime Chevallier.

Des enregistrements vidéo ont été faits mais ils ne sont pas encore disponibles.


L'article seul

Survey of the DNS servers in the fediverse

First publication of this article on 18 November 2022


The fediverse is supposed to be decentralized. But the fact that the network has a decentralized architecture does not mean that it is perfectly decentralized in practice. We survey here the DNS authoritative servers for the domains used by the fediverse instances. Unfortunately, yes, they are too concentrated.

Mostly because of the incredibly childish behaviour of Elon Musk, the current wave of migration from Twitter to the fediverse is much larger than the previous ones. As a result, there is a renewed interest in the fediverse, a system which is far from recent. The fediverse is made of various instances, each one being independently administered, both from a technical point of view, and from a political one. Nothing new or extraordinary, this is how the Internet was architectured, and how its services worked, before a few centralized US-based giants distorted the vision of many people, making them believe that centralization is normal. Each fediverse instance has a domain name and this domain name is hosted on a set of authoritative name servers, which will reply to the DNS requests of the clients, the DNS resolvers. As an example, the instance I use, mastodon.gougere.fr is in the domain gougere.fr, whose set of authoritative name servers is currently composed of three servers. Unix users can for instance display them with dig :

% dig +short NS gougere.fr  
nsb.bookmyname.com.
nsa.bookmyname.com.
nsc.bookmyname.com.
  

Note you can also perform DNS queries on the fediverse itself, as explained here (see the current result).

A domain "works", for the users, only if the authoritative name servers reply, and reply properly. This makes them critical infrastructure for an instance. If your DNS authoritative servers fail, or lie, the instance is unusable, even if the Pleroma, Mastodon or other server still works fine. So, what are the DNS servers used for this critical role in the current fediverse?

To get data, I started from a list of current instances. I got one from instances.social. Note that, on a decentralized network like the fediverse, you cannot get an official and complete list of all instances. All lists are imperfect. But I have to start from somewhere (other possibilities: http://demo.fedilist.com/instance or the public list of peers of a big instance such as https://mastodon.social/api/v1/instance/peers). So, I created an authentication token on instances.social, and retrieved a list with curl:


% curl -s -H "Authorization: Bearer MY-TOKEN" https://instances.social/api/1.0/instances/list\?count=10000\&include_down=false > list.json 

I included only live instances (include_down=false) but, still, some instance names already were missing from the DNS.

The resulting list was in the JSON format and processed by two Python scripts (see the code later). The first one queried the DNS (with the excellent dnspython library) to get information such as the list of authoritative name servers and their IP addresses. The second script processed the JSON data of the first to output meaningful statistics. Of course, like with any quantitative survey, the hard problem is often which questions to ask, rather than the answers.

Let's start with the actual numbers:

%./nameservers-fediverse-analyze.py
There are 1596 instances
  

Remember that the list is incomplete (no one knows how many instances really live). Some of these instances are grouped into the same registered domain (the domain you bought from a registry, may be through a registrar). I choosed to group the instance names in the DNS zones (a set of contiguous names hosted on the same set of name servers). For instance, medievalist.masto.host and piano.masto.host are in the same zone, masto.host, they have the same servers. There are a bit less zones than instance names:

There are 1596 instances in 1540 zones. The largest zone, masto.host., encompasses 10 instances
  

Except masto.host, there are few fediverse hosters with such subdomains, so no sign of concentration/centralisation here. There is no giant fediverse hoster.

Now, the name servers themselves:

There are 2062 nameservers. The largest one, dns2.registrar-servers.com., hosts 85 instances.
  

Some name servers are more common, such as this dns2.registrar-servers.com, used by a big DNS hoster. But it is still only for a small minority of instances.

But wait, name servers come in groups, called sets. The instance administrator, except if he or she is a DNS fan, typically does not cherry-pick his or her name servers, they use a set indicated by the DNS hoster. Let's study sets instead:

There are 951 nameserver sets. The largest one, dns1.registrar-servers.com.;dns2.registrar-servers.com., hosts 85 instances. The top 10 % hosts 677 instances.
  

There are less sets than instances (a sign of a small centralisation) but not by a large margin. The biggest set is not so big but we note that the top tenth of the sets hosts more than a third of the instances. There are not so many DNS providers.

But there is a catch. Some DNS hosters use a lot of names so you may think there are many various sets but they actually depend on one company, a bad thing for decentralisation. The typical example is Cloudflare, with a lot of cool names depending on the domain they host (eleanor.ns.cloudflare.com, sue.ns.cloudflare.com, etc). So, we have to group the name servers by company. One of the simplest ways is to find the registered domain of the name servers' names. To do so, we use the Public Suffix List, which is not authoritative and not perfect but sufficient for us. It will put all the Cloudflare names into one bucket, the registered domain cloudflare.com:

There are 667 nameserver's domains. The largest one, cloudflare.com, hosts 366 instances. The top 10 % hosts 1324 instances

This time, we have a clear sign of centralisation. A lot of seemingly independent instances have a shared provider, Cloudflare. If Cloudflare breaks, or does evil things, it will affect many instances. And the top 10 % of hosters serves the overwhelming majority of fediverse instances. For your information, the biggest ones are:

  • cloudflare.com: 366
  • registrar-servers.com: 91
  • gandi.net: 90
  • domaincontrol.com: 55
  • googledomains.com: 49
  • digitalocean.com: 48
  • linode.com: 39
  • inwx.de: 23
  • inwx.eu: 23

(It can be noticed that, unlike the fediverse itself, which uses a lot of "new TLDs" such as .social, the name servers are faithful to the old TLDs.) Playing with names, as seen in the Cloudflare case, complicated things. We have also the case of AWS which does not appear on the above list but they should; they use a lot of registered domains (awsdns-46.co.uk, awsdns-20.com, etc) so they appear as many different hosters. Also, about Cloudflare, remember that this survey is only about the DNS: I did not try to see where the server instance is hosted. I repeat: just DNS, no HTTP was used or considered here. (One also could note that some authoritative name servers are installed at one big machine hoster, which gives Cloudflare an even more prominent place.)

So, as you can see, but this is hardly a surprise, the fediverse is not perfect and there are signs of centralisation, at least as far as DNS is concerned.

The two programs used for this article are:

  • nameservers-fediverse-gather.py: it gathers information from the DNS and produces a JSON file storing, for each instance, its DNS zone, the name servers and their IP addresses (which were not used for this article). To find the DNS zone, it just climbs the DNS tree until it founds name server records.
  • nameservers-fediverse-analyze.py: it reads the JSON file produced by the first program and displays statistics.

Among the weaknesses of this analysis, you'll note that our instances are considered equal. But there are very small and very big instances. It could be useful to tweak results depending on the size of the instance. instances.social gives us this information but it has to be handled with care, it is just a declaration from the instance (and many accounts may be inactive, anyway).


L'article seul

Peut-on censurer tout en respectant la vie privée ?

Première rédaction de cet article le 10 novembre 2022


C'était le thème d'une réunion pendant l'IETF 115 à Londres. Pour censurer sur l'Internet, le censeur doit regarder ce à quoi l'utilisateur voulait accéder, ce qui pose des problèmes de vie privée évidents. Peut-on censurer sans violer la vie privée ?

Le problème était posé sous la forme d'une question technique mais, évidemment, c'est plus compliqué que cela. Avant de détailler tous les aspects, voyons comment fonctionne un mécanisme de censure simple : l'utilisateur veut visiter https://truc-interdit.example/, les équipements intermédiaires sur le réseau voient cette demande, la comparent à une liste de choses interdites et bloquent éventuellement la visite. On peut réaliser cela de plusieurs façons, par exemple par un résolveur DNS menteur ou par un relais HTTP qui regarde les URL. Peu importe : dans tous les cas, des équipements intermédiaires apprendront qu'on voulait accéder à telle ou telle ressource. C'est ennuyeux pour les droits humains et ça rentre en conflit avec des techniques comme DoT ou HTTPS, qui sont justement là pour empêcher ce genre d'interceptions.

Mes lectrices et lecteurs informaticien·nes peuvent faire une pause ici et se demander comment techniquement réaliser une telle interception sans apprendre l'identité de la ressource à laquelle on voulait accéder. C'est un exercice intellectuellement stimulant. Mais, je le dis tout de suite, le problème n'est pas purement technique. En attendant d'élargir la question, je vous laisse quelques minutes pour réfléchir à une solution technique.

C'est bon, les quelques minutes sont écoulées ? Une solution possible serait, pour le cas du Web, que votre navigateur condense l'URL et envoie cet URL condensé à un service de filtrage qui répond Oui ou Non. Cela préserverait la vie privée, tout en permettant le filtrage. (Oui, il y a quelques détails techniques à prendre en compte, il faut saler pour éviter la constitution de dictionnaires, et il faut canonicaliser pour éviter qu'un malin ne fasse varier les URL pour éviter la détection, etc. Mais ne laissez pas ces détails techniques vous absorber trop longtemps.)

Le vrai problème de cette technique ? Elle nécessite la coopération de l'utilisateur (plus exactement de ses logiciels). C'était le point soigneusement dissimulé par le groupe qui avait organisé cette réunion, l'Internet Watch Foundation. (Au passage, cette réunion était un side meeting, réunion qui utilise les salles de l'IETF mais qui n'est pas forcément approuvée par l'IETF.) Le titre officiel de la réunion était « Is Privacy preserving Web Filtering Possible? » alors qu'en fait le désir n'était pas de filtrer mais de censurer (le filtrage se fait avec le consentement de l'utilisateur, la censure contre son gré). Le présentateur avait introduit la réunion en disant que le but était d'éviter qu'un internaute innocent ne soit exposé contre son gré à des images pédo-pornographiques. Si c'était vraiment le cas, le problème serait simple : un logiciel de filtrage sur la machine de l'utilisateur, comme on fait avec les bloqueurs de publicité. Le cas de la pédo-pornographie est un peu plus complexe car, contrairement aux bloqueurs de publicité, on ne souhaite pas distribuer les listes d'URL bloqués (des pédophiles pourraient les utiliser pour aprendre de nouvelles ressources). Mais ce n'est pas un gros problème, la solution du service qu'on interroge en lui envoyant un condensat de l'URL suffit.

Non, le vrai problème, c'est le consentement de l'utilisaeur. Si vraiment, on veut rendre un service à l'utilisateur, le problème est relativement simple. Si on veut censurer sans l'accord de l'utilisateur, cela devient effectivement plus compliqué. L'argument comme quoi on voulait protéger l'utilisateur contre une exposition accidentelle ne tient donc pas.

L'Internet Watch Foundation a présenté le problème comme purement technique, sachant très bien que les participant·es à l'IETF sont passionné·es de technique et vont sauter sur l'occasion, cherchant des solutions complexes et intéressantes (par exemple du côté du chiffrement homomorphique, la solution miracle souvent citée). Mais on est là dans un cas où la question n'est pas technique, mais politique. Il n'y a aucun moyen magique de censurer sans le consentement de l'utilisateur et en respectant sa vie privée. Le but de l'IWF était probablement justement de montrer qu'il n'y avait pas de solution technique (« on a été de bonne volonté on a soumis, la question à l'IETF ») et qu'il fallait donc censurer sans se soucier de vie privée.

Notons aussi qu'une infrastructure de censure, une fois en place, sert à beaucoup de choses. La pédo-pornographie, par l'horreur qu'elle suscite, sert souvent à couper court aux débats et à mettre en place des systèmes de censure, qui seront ensuite utilisés pour bien d'autres choses.


L'article seul

IETF 115 Hackathon, DNS error reporting

First publication of this article on 8 November 2022


At the IETF 115 hackathon in London, I worked on DNS error reporting.

Remember that IETF develops the standards for the Internet, roughly from layer 3 to some parts of layer 7. One of the most important technologies at IETF is of course the DNS. The role of the IETF hackathons, that take place the weekend just before the IETF meeting, is to try to implement new ideas (not yet standardized) to see if it works (IETF prides itself on relying on "running code"). This time, we were four persons working on DNS error reporting. This technique is currently specified in an Internet Draft draft-ietf-dnsop-dns-error-reporting. The problem it tries to solve is the one of informing the managers of a zone that there is some technical problem detected by a DNS resolver. Today, if you don't test your zone thoroughly with tools like DNSviz or Zonemaster, you'll know that there is a problem only when users will complain on Twitter. It would be better to be told by your clients, the resolvers, before that.

The way it works is a follows:

  • The authoritative server for the zone returns in its replies an EDNS option indicating a report receiving domain.
  • The resolver, when it detects a problem, such as a wrong DNSSEC key, sees this indication and creates a DNS query encoding the problem and appending the report receiving domain.
  • The servers for the report receiving domain receives the query, processes it and stores it.
  • The zone administrator reads about the problem and acts accordingly.

You can see there are three actors and therefore three programs to modify (the last ones, the servers for the report receiving domain, could be unmodified authoritative name servers). I worked on the authoritative side, using the experimental name server Drink. Willem Toorop worked on the Unbound resolver and Shane Kerr on another (proprietary) authoritative server. Roy Arends helped, replied and modified the draft with our feedback.

The draft is quite simple and straightforward. Drink was modified to emit a new EDNS option:


report_to = Binary.from_list(encode(config()["reporting-agent"]))
length = byte_size(report_to)
Binary.append(<<Drink.EdnsCodes.reporting::unsigned-integer-size(16),
	length::unsigned-integer-size(16)>>, report_to)

  

And then to process the reports (the query type for the reports is TXT):

"report" ->
   if config()["services"]["report"] do
              case qtype do
                :txt ->
                  result = Drink.Reports.post(Enum.join(qname, "."))
                  %{:rcode => Drink.RCodes.noerror,
                    :data => [result],
                    :ttl => @report_ttl}
                :any -> @any_hinfo
                _ -> %{:rcode => Drink.RCodes.noerror}
              end
  

Reports are stored in a separate agent. Note that we check they are properly formed, with the label _er at the beginning and at the end:

  def post(value) do
    labels = String.split(value, ".")
    if List.last(labels) == "report" do
      labels = List.delete_at(labels, length(labels)-1) # Remove report
      if List.first(labels) == "_er" and List.last(labels) == "_er" do
        labels = List.delete_at(List.delete_at(labels, length(labels)-1), 0) # Remove _er
        error_code = List.last(labels) 
        qtype = List.first(labels) 
        qname = Enum.join(List.delete_at(List.delete_at(labels, length(labels)-1), 0), ".")
        Agent.update(__MODULE__,
          fn state ->
            [{error_code, qtype, qname} | state]
          end)
        "Thanks for the report of error #{error_code} on #{qname}"
      else
        # Else do nothing, probably QNAME minimization (or may be broken report)
        "Invalid report"
      end
    end
  end
  

From the outside, this looks like this:


% dig _er.1.foobar.example.9._er.report.dyn.courbu.re TXT
...
;; ANSWER SECTION:
_er.1.foobar.example.9._er.report.dyn.courbu.re. 24 IN TXT "Thanks for the report of error 9 on foobar.example"

;; AUTHORITY SECTION:
dyn.courbu.re.		15	IN	NS	dhcp-80f2.meeting.ietf.org.
...

  

Since Drink is fully dynamic, it allows us to send a response depending on the analysis of the report (but the draft says you can return anything, you can use a static TXT record). If the report is invalid:

% dig _er.1.foobar.example.9.missing-end.report.dyn.courbu.re TXT
...
;; ANSWER SECTION:
_er.1.foobar.example.9.missing-end.report.dyn.courbu.re. 30 IN TXT "Invalid report"
    

One can then retrieve the report, by asking the name server:

%   echo report | socat - UNIX-CONNECT:/home/stephane/.drink.sock
REPORT state:
foobar.example, 9
funny.example, 9;2
    

Lessons from the hackathon? Drink is well suited for rapid exprimentation (it was one of its goals), the draft is clear and simple and interoperability is fine. By querying the modified Unbound resolver, we see report queries arriving and being accepted. Here, a DNSSEC error:


% dig @2a04:b900:0:100::28 www.bogus.bortzmeyer.fr
...
;; OPT PSEUDOSECTION:
...
; EDE: 9 (DNSKEY Missing): (validation failure <www.bogus.bortzmeyer.fr. A IN>: key for validation bogus.bortzmeyer.fr. is marked as invalid because of a previous validation failure <www.bogus.bortzmeyer.fr. A IN>: No DNSKEY record from 2001:41d0:302:2200::180 for key bogus.bortzmeyer.fr. while building chain of trust)
...

     

(Errors are the errors specified in RFC 8914.) And Drink saw:

nov. 08 18:17:14 smoking Drink[365437]: [info]  185.49.141.28 queried for _er.1.www.bogus.bortzmeyer.fr.9._er.report.dyn.courbu.re/txt
     
% echo report | socat - UNIX-CONNECT:/home/stephane/.drink.sock
REPORT state:
foobar.example, 9
funny.example, 9;2
www.bogus.bortzmeyer.fr, 9
     

So, it is a success. You can see all the reports made at the end of the hackathon (ours is "IETF115-DNS-Hackathon-Results.pdf "). Outside of that, there is a Wireshark implementation of the EDNS option: wireshark-dns-error-reporting.jpeg

Because it is a work in progress, note that there were changes before, during and after the hackathon. The query type for the reports changed from NULL to TXT (the code for NULL was tested and works), the EDNS option is now returned only when the resolver asks for it (this is not yet done in Drink), the format of the query report changed, etc. The goal is to test, not to make stable code.

The code, written in Elixir, is available online. It is not yet merged in the master, you have to use the git branch error-reporting.


L'article seul

An IPC server (with Unix sockets) in Elixir

First publication of this article on 7 November 2022


While adding IPC support to an Internet server written in Elixir, I did not find a lot of documentation or examples about Unix sockets with Elixir. So, here it is.

Unix sockets (also mentioned as "domain" or "local") are a way to communicate between processes (hence IPC = Inter-Process Communication) on the same machine. As such, they are less general than TCP/IP sockets but they are more secure (you cannot connect from the outside, and you can retrieve the process identifier, user identity, etc of the client) and you can finely control access through normal file permissions since the socket appears as a file.

To have the server written in Elixir listen on such a socket, we first clean (we use the standard module File):

    
sockname = System.get_env("HOME") <> "/.test-ipc.sock"
File.rm(sockname) # We don't test the results, since we are not interested
   # if the file existed or not.

  

Then we can open the socket, using the Erlang socket library, indicating the type :local :


{:ok, s} = :socket.open(:local, :stream, %{})
:ok = :socket.bind(s, %{family: :local, path: sockname})
# You can add File.chmod here to set permissions
:ok = :socket.listen(s)

Note that we use pattern matching to be sure the operations succeeded (we would have better error messages with a code testing the different cases).

Now, we can accept connections (the rest of the code is not specific to Unix local sockets):

{:ok, client} = :socket.accept(socket)
  

And read from the clients, and write to them (here, we just echo the message), like we would do with more common TCP/IP sockets:

    
{:ok, result} = :socket.recv(socket, 0)
msg = String.trim(result)
Logger.info("Received \"#{msg}\"")

:ok = :socket.send(socket, msg <> "\r\n")

  

A complete echo server using these techniques is ipc-serve.exs. Just run it:

% elixir server.exs
Listening on /home/stephane/.test-ipc.sock
  

Clients can be netcat:

% netcat -U ~/.test-ipc.sock  

allo
allo


Tu es là?
Tu es là?
  

Or socat:

% echo foobar | socat - UNIX-CONNECT:/home/stephane/.test-ipc.sock
foobar
  

Or of course a client you write yourself.


L'article seul

RFC 9299: An Architectural Introduction to the Locator/ID Separation Protocol (LISP)

Date de publication du RFC : Octobre 2022
Auteur(s) du RFC : A. Cabellos (UPC-BarcelonaTech), D. Saucez (INRIA)
Pour information
Réalisé dans le cadre du groupe de travail IETF lisp
Première rédaction de cet article le 3 novembre 2022


Le protocole réseau LISP (Locator/ID Separation Protocol, rien à voir avec le langage de programmation du même nom) est normalisé dans le RFC 6830 et plusieurs autres qui l'ont suivi. Ce RFC 6830 est un peu long à lire et ce nouveau RFC propose donc une vision de plus haut niveau, se focalisant sur l'architecture de LISP. Cela peut donc être un bon point de départ vers les RFC LISP.

L'idée de base de LISP est de séparer l'identificateur du localisateur, comme recommandé par le RFC 4984. Un identificateur désigne une machine, un localisateur sa position dans l'Internet. Aujourd'hui, les adresses IP servent pour les deux, ne satisfaisant parfaitement aucun des deux buts : les identificateurs devraient être stables (une machine qui change de réseau ne devrait pas en changer), les localisateurs devraient être efficaces et donc être liés à la topologie, et agrégeables.

LISP a donc deux classes : les identificateurs, ou EID (End-host IDentifier) et les localisateurs, ou RLOC (Routing LOCators). Les deux ont la syntaxe des adresses IP (mais pas leur sémantique). Un système de correspondance permet de passer de l'un à l'autre (par exemple, je connais l'EID de mon correspondant, je cherche le RLOC pour lui envoyer un paquet). LISP est plutôt prévu pour être mis en œuvre dans les routeurs, séparant un cœur de l'Internet qui n'utilise que les RLOC, d'une périphérie qui utiliserait les EID (avec, entre les deux, les routeurs LISP qui feraient la liaison).

En résumé (accrochez-vous, c'est un peu compliqué) :

  • LISP peut être vu comme un réseau virtuel (overlay) au-dessus de l'Internet existant (underlay),
  • Les RLOC n'ont de sens que dans le réseau sous-jacent, l'underlay,
  • Les EID n'ont de sens que dans le réseau virtuel overlay,
  • Entre les deux, le système de correspondance,
  • Dans le réseau sous-jacent, les RLOC servent de localisateur et d'identificateur,
  • Dans un site à la périphérie, les EID servent d'identificateur et de localisateur pour les autres machines du site.

C'est ce côté « solution dans les routeurs » et donc le fait que les machines terminales ne savent même pas qu'elles font du LISP, qui distingue LISP des autres solutions fondées sur la séparation de l'identificateur et du localisateur, comme ILNP (RFC 6740).

Au passage, pourquoi avoir développé LISP ? Quel était le problème à résoudre ? Outre la beauté conceptuelle de la chose (il n'est pas esthétique de mêler les fonctions d'identificateur et de localisateur), le principal problème à traiter était celui du passage à l'échelle du système de routage de l'Internet, décrit dans le RFC 4984. En gros, le nombre de routes distinctes augmente trop vite et menace la stabilité des routeurs de la DFZ. Le but était donc, par une nouvelle architecture, de rendre inutile certains choix qui augmentent la taille de la table de routage (comme la désagrégation des préfixes IP, afin de faire de l'ingénierie de trafic). Le RFC 7215 décrit comment LISP aide dans ce cas.

La section 7 du RFC décrit les différents scénarios d'usage de LISP :

  • Ingénierie de trafic : aujourd'hui, dans l'Internet classique BGP, quand on veut orienter le trafic entrant, le faire passer par un chemin donné, on utilise souvent la désagrégation des préfixes. Comme indiqué plus haut, cela aggrave la pression sur la table de routage globale. L'indirection que permet LISP dispense de cette solution : on peut publier dans le système de correspondance les RLOC qu'on veut.
  • Transition vers IPv6 : les EID et les RLOC ont la forme syntaxique d'une adresse IP, v4 ou v6, et il n'y a pas d'obligation qu'EID et RLOC aient la même version. On peut avoir des EID IPv6 et des RLOC IPv4, et cela fournit un mécanisme de tunnel permettant de connecter deux sites LISP IPv6 au-dessus de réseaux qui seraient purement IPv4. L'avantage par rapport aux techniques de transition actuelles est l'intégration dans une solution plus générale et plus « propre ».
  • LISP permet également de faire des VPN, ou de gérer la mobilité de réseaux entiers (un réseau qui se déplace, et donc change de RLOC, c'est juste la correspondance dans le sous-système de contrôle qu'il faut changer, et tout les partenaires routeront vers les nouveaux RLOC).

La section 3 du RFC décrit en détail l'architecture de LISP (après, vous serez mûr·e·s pour lire les RFC LISP eux-mêmes, en commençant par le RFC 6830). Elle repose sur quatre principes :

  • La séparation entre l'identificateur (le EID) et le localisateur (le RLOC),
  • Deux sous-systèmes différents, le contrôle et les données, utilisant des protocoles différents, et pouvant évoluer séparement (enfin, dans une certaine mesure),
  • Une architecture overlay : LISP est déployé sur un réseau virtuel au-dessus de l'Internet existant, ce qui évite les approches « table rase », qui ont une probabilité de déploiement à peu près nulle,
  • Un protocole déployable de manière incrémentale, pas besoin d'attendre que tout le monde s'y mette, il y a des avantages à déployer LISP même pour les premiers à l'adopter (ce problème de déploiement est une des plaies de l'Internet actuel, comme on le voit avec IPv6).

La séparation entre identificateur et localisateur n'est pas faite au niveau de la machine individuelle, comme avec ILNP, mais à la frontière entre la périphérie de l'Internet (the edge) et son cœur (the core, en gros, la DFZ et quelques routeurs en plus). La périphérie travaille avec des EID (elle ne sait même pas que LISP est utilisé), le cœur avec des RLOC. Contrairement à ILNP, il n'y a donc pas une stricte séparation entre identificateurs et localisateurs : ils ont la même syntaxe (qui est celle d'une adresse IP, v4 ou v6) et, à part les routeurs d'entrée et de sortie des tunnels LISP, personne ne sait s'il utilise un EID ou un RLOC : les machines terminales manipulent des EID, les routeurs du cœur des RLOC, tout en croyant que ce sont des adresses IP ordinaires. Seuls les routeurs à la frontière entre les deux mondes connaissent LISP (et auront donc besoin d'un logiciel adapté).

Un Internet LISP est donc une série de « sites LISP » (des réseaux de périphérie accessibles par LISP) connectés par des tunnels entre eux, au-dessus du cœur actuel. Le routeur d'entrée du tunnel se nomme ITR (pour Ingress Tunnel Router) et celui de sortie ETR (pour Egress Tunnel Router). Le terme de xTR (pour « ITR ou bien ETR ») est parfois utilisé pour désigner un routeur LISP, qu'il soit d'entrée ou de sortie lisp-arch

Le sous-système des données (data plane) se charge d'encapsuler et de décapsuler les paquets, puis de les transmettre au bon endroit. (Sa principale qualité est donc la rapidité : il ne faut pas faire attendre les paquets.) Les ITR encapsulent un paquet IP qui vient d'un site LISP (dans un paquet UDP à destination du port 4341), puis l'envoient vers l'ETR. À l'autre bout du tunnel, les ETR décapsulent le paquet. Dans le tunnel, les paquets ont donc un en-tête intérieur (un en-tête IP normal, contenant les EID source et destination), qui a été placé par la machine d'origine, et un en-tête extérieur (contenant le RLOC source, celui de l'ITR, et le RLOC de destination, celui de l'ETR puis, après l'en-tête UDP, l'en-tête spécifique de LISP). Rappelez-vous que les routeurs du cœur ne connaissent pas LISP, ils font suivre ce qui leur semble un paquet IP ordinaire. Les routeurs LISP utilisent des tables de correspondance entre EID et RLOC pour savoir à quel ETR envoyer un paquet.

Ces tables ont été apprises du sous-système de contrôle (control plane, qui contient la fonction de correspondance - mapping), le routeur ayant un cache des correspondances les plus récentes. Cette fonction de correspondance, un des points les plus délicats de LISP (comme de tout système de séparation de l'identificateur et du localisateur) est décrite dans le RFC 6833. Son rôle peut être comparé à celui du DNS et de BGP dans l'Internet classique.

Une correspondance est une relation entre un préfixe d'identificateurs (rappelez-vous que les EID sont, syntaxiquement, des adresses IP ; on peut donc utiliser des préfixes CIDR) et un ensemble de localisateurs, les RLOC des différents routeurs possibles pour joindre le préfixe convoité.

Le RFC 6833 normalise une interface avec ce système de correspondance. Il y a deux sortes d'entités, le Map Server, qui connait les correspondances pour certains préfixes (car les ETR lui ont raconté les préfixes qu'ils servent), et le Map Resolver, qui fait les requêtes (il est typiquement dans l'ITR, ou proche). Quatre messages sont possibles (les messages de contrôle LISP sont encpasulés en UDP, et vers le port 4342) :

  • Map-Register : un ETR informe son Map Server des préfixes EID qu'il sait joindre,
  • Map-Notify : la réponse de l'ETR au message précédent,
  • Map-Request : un ITR (ou bien un outil de débogage comme lig, cf. RFC 6835) cherche les RLOC correspondant à un EID,
  • Map-Reply : un Map Server ou un ETR lui répond.

Un point important de LISP est qu'il peut y avoir plusieurs mécanismes de correspondance EID->RLOC, du moment qu'ils suivent les messages standard du RFC 6833. On pourra donc, dans le cadre de l'expérience LISP, changer de mécanisme pour voir, pour tester des compromis différents. Notre RFC rappele l'existence du système ALT (RFC 6836, fondé, comme BGP sur un graphe. Mais aussi celle d'un mécanisme utilisant une base « plate » (NERD, RFC 6837), un mécanisme arborescent nommé DDT (RFC 8111), des DHT, etc. On pourrait même, dans des déploiements privés et locaux, avoir une base centralisée avec un seul serveur.

ALT, normalisé dans le RFC 6836, est le système de correspondance « historique » de LISP, et il est souvent présenté comme le seul dans les vieux documents. Il repose sur BGP, protocole bien maitrisé par les administrateurs de routeurs, ceux qui auront à déployer LISP. L'idée de base est de connecter les serveurs ALT par BGP sur un réseau virtuel au-dessus de l'Internet.

DDT, dans le RFC 8111, lui, ressemble beaucoup plus au DNS, par sa structuration arborescente des données, et sa racine.

Évidemment, tout l'Internet ne va pas migrer vers LISP instantanément. C'est pour cela que notre RFC mentionne les problèmes de communication avec le reste du monde. Les EID ne sont typiquement pas annoncés dans la table de routage globale de l'Internet. Alors, comment un site pourra-t-il communiquer avec un site LISP ? Le mécanisme décrit dans le RFC 6832 utilise deux nouvelles sortes de routeurs : les PITR (Proxy Ingress Tunnel Router) et les PETR (Proxy Egress Tunnel Router). Le PITR annonce les EID en BGP vers l'Internet, en les agrégeant le plus possible (l'un des buts de LISP étant justement d'éviter de charger la table de routage globale). Il recevra donc les paquets envoyés par les sites Internet classiques et les fera suivre par les procédures LISP normales. A priori, c'est tout : le site LISP peut toujours envoyer des paquets vers l'Internet classiques en ayant mis un EID en adresse IP source. Mais cela peut échouer pour diverse raisons (uRPF, par exemple) donc on ajoute le PETR : il recevra le paquet du site LISP et le transmettra.

Voici pour les principes de LISP. Mais, si vous travaillez au quotidien comme administrateur d'un réseau, vous avez sans doute à ce stade plein de questions concrètes et opérationnelles. C'est le moment de lire la section 4 de ce RFC. Par exemple, la gestion des caches : un routeur LISP ne peut pas faire appel au système de correspondance pour chaque paquet qu'il a à transmettre. Le sous-système des données tuerait complètement le sous-système de contrôle, si un routeur s'avisait de procéder ainsi. Il faut donc un cache, qui va stocker les réponses aux questions récentes. Qui dit cache dit problèmes de cohérence des données, puisque l'information a pu changer entre la requête, et l'utilisation d'une réponse mise en cache. Pour gérer cette cohérence, LISP dispose de divers mécanismes, notamment un TTL (Time To Live) : l'ETR le définit, indiquant combien de temps les données peuvent être utilisées (c'est typiquement 24 h, aujourd'hui).

Autre problème pratique cruciale, la joignabilité des RLOC. C'est bien joli de savoir que telle machine a tel RLOC mais est-ce vrai ? Peut-on réellement lui parler ou bien tous les paquets vont-ils finir dans un trou noir ? Un premier mécanisme pour transporter l'information de joignabilité est les LSB (Locator Status Bits). Transportés dans les paquets LISP, ces bits indiquent si un RLOC donné est joignable par l'ETR qui a envoyé le paquet. Évidemment, eux aussi peuvent être faux, donc, s'il existe une communication bi-directionnelle, il est préférable d'utiliser le mécanisme des numniques. Quand un ITR écrit à un ETR, il met un numnique dans le paquet, que l'ETR renverra dans son prochain paquet. Cette fois, plus de doute, l'ETR est bien joignable. Si l'ITR est impatient et veut une réponse tout de suite, il peut tester activement la joignabilité, en envoyant des Map-Request.

LISP est souvent présenté avec un modèle simplifié où chaque site est servi par un seul ETR, qui connait les EID du site et les annonce au Map Server. Mais, dans la réalité, les sites sérieux ont plusieurs ETR, pour des raisons de résilience et de répartition de charge. Cela soulève le problème de leur synchronisation : ces ETR doivent avoir des configurations compatibles, pour annoncer les mêmes RLOC pour leurs EID. Pour l'instant, il n'existe pas de protocole pour cela, on compte sur une synchronisation manuelle par l'administrateur réseaux.

Enfin, comme LISP repose sur des tunnels, il fait face à la malédiction habituelle des tunnels, les problèmes de MTU. Du moment qu'on encapsule, on diminue la MTU (les octets de l'en-tête prennent de la place) et on peut donc avoir du mal à parler avec les sites qui ont une MTU plus grande, compte-tenu de la prévalence d'erreurs grossières de configuration, comme le filtrage d'ICMP. La section 4.4 de notre RFC décrit le traitement normal de la MTU dans LISP et ajoute que des mécanismes comme celui du RFC 4821 seront peut-être nécessaires.

Dans l'Internet d'aujourd'hui, une préoccupation essentielle est bien sûr la sécurité : d'innombrables menaces pèsent sur les réseaux (section 8 du RFC). Quelles sont les problèmes spécifiques de LISP en ce domaine ? Par exemple, certains systèmes de correspondance, comme DDT, sont de type pull : on n'a pas l'information à l'avance, on va la chercher quand on en a besoin. Cela veut dire qu'un paquet de données (sous-système des données) peut indirectement déclencher un événement dans le sous-système de contrôle (par la recherche d'une correspondance EID->RLOC afin de savoir où envoyer le paquet). Cela peut affecter la sécurité.

D'autant plus que le sous-système de contrôle sera typiquement mis en œuvre dans le processeur généraliste des routeurs, beaucoup moins rapide que les circuits électroniques spécialisés qui servent à la transmission des données. Un attaquant qui enverrait des tas de paquets vers des EID différents pourrait, à un coût très faible pour lui, déclencher plein de demandes à DDT, ralentissant ainsi sérieusement les routeurs LISP. Une mise en œuvre naïve de LISP où toute requête pour un EID absent du cache déclencherait systématiquement une MAP-Request serait très vulnérable. Une limitation du trafic est donc nécessaire.

En parlant du système de correspondance, il représente évidemment un talon d'Achille de LISP. Si son intégrité est compromise, si des fausses informations s'y retrouvent, les routeurs seront trahis et enverront les paquets au mauvais endroit. Et si le système de correspondance est lent ou en panne, par exemple suite à une attaque par déni de service, le routage ne se fera plus du tout (à part pour les EID encore dans les caches des routeurs). On peut donc comparer ce système de correspondance au DNS dans l'Internet classique, par son côté crucial pour la sécurité.

Il faut donc des « bons » Map Server, qui suivent bien le RFC 6833 (notamment sa section 6) et, peut-être dans le futur, des Map Servers qui gèrent l'extension de sécurité LISP-Sec (si son RFC est publié un jour).

Dernier point de sécurité, le fait que LISP puisse faire du routage asymétrique (le chemin d'Alice à Bob n'est pas le même que celui de Bob à Alice). Rien d'extraordinaire à cela, c'est pareil pour lee routage Internet classique, mais il faut toujours se rappeler que cela a des conséquences de sécurité : par exemple, un pare-feu ne verra, dans certains cas, qu'une partie du trafic.

On trouvera plus de détails sur les attaques qui peuvent frapper LISP dans le RFC 7835.

Pour ceux qui sont curieux d'histoire des technologies, l'annexe A du RFC contient un résumé de LISP. Tout avait commencé à Amsterdam en octobre 2006, à l'atelier qui avait donné naissance au RFC 4984. Un groupe de participants s'était alors formé, avait échangé, et le premier Internet-Draft sur LISP avait été publié en janvier 2007. En même temps, et dans l'esprit traditionnel de l'Internet (running code), la programmation avait commencé et les premiers routeurs ont commencé à gérer des paquets LISP en juin 2007.

Le groupe de travail IETF officiel a été ensuite créé, en mars 2009. Les premiers RFC sont enfin sortis en 2013.

LISP n'a pas toujours été comme aujourd'hui ; le protocole initial était plutôt une famille de protocoles, désignés par des numéros, chacun avec des variantes sur le concept de base. Cela permettait de satisfaire tous les goûts mais cela compliquait beaucoup le protocole. On avait LISP 1, où les EID étaient routables dans l'Internet normal (ce qui n'est plus le cas), LISP 1.5 où ce routage se faisait dans un réseau séparé, LISP 2 où les EID n'étaient plus routables, et où la correspondance EID->RLOC se faisait avec le DNS, et enfin LISP 3 où le système de correspondance était nouveau (il était prévu d'utiliser une DHT...). Le LISP final est proche de LISP 3.


Téléchargez le RFC 9299


L'article seul

Il y a des cas où la chaine de blocs n'est pas utile

Première rédaction de cet article le 21 octobre 2022


La chaine de blocs, malgré quelques soubresauts et critiques, reste aujourd'hui un puissant argument marketing. On voit par exemple une université se vanter d'attester ses diplômes sur cette chaine, et son service de la communication pense apparemment que cela va jouer en sa faveur. Il est vrai que la chaine de blocs résout élégamment des problèmes auparavant considérés comme difficiles, voire impossibles, par exemple le triangle de Zooko. Mais, comme tous les outils, la chaine de blocs ne résout pas tous les problèmes. Voyons un cas où elle n'apporte rien, qui est justement le cas d'usage de l'université citée plus haut.

La chaine de blocs n'est pas simplement une liste chainée de données. Si c'était le cas, elle n'aurait rien d'intéressant, cette structure de données étant une des plus anciennes qui soit. De même, le fait que les transactions contenues dans le bloc soient signées est assez banal, les signatures numériques sont un concept ancien et répandu bien avant l'invention de la chaine de blocs. L'intérêt de la chaine de blocs est ailleurs : dans le fait qu'elle est pair-à-pair, que n'importe qui puisse y écrire, et qu'un consensus émerge entre des entités qui ne se font pas mutuellement confiance. C'est ainsi que fonctionne son utilisation la plus emblématique, les cryptomonnaies. Les gens qui acquièrent et dépensent les jetons de la cryptomonnaie utilisée ne se font pas confiance et ne se connaissent même pas. Il faut pourtant arriver à un consensus sur le fait qu'Alice ait trois jetons et, qu'après en avoir donné un à Bob, elle n'en a plus que deux (alors qu'Alice préfererait qu'on croit qu'elle en a toujours trois). C'est cela que permet la chaine de blocs et, dans ce cas d'usage, elle est irremplaçable. (Le tout est bien expliqué dans l'article de Satoshi Nakamoto qui décrit le Bitcoin, et qui a intoduit le concept de chaine de blocs. Ironiquement, le terme de blockchain n'apparait pas dans cet article.)

Le but affiché des cryptomonnaies est justement de réaliser une monnaie qui ne dépende pas d'une autorité extérieure, par exemple une banque centrale. Mais si on a une telle autorité et qu'on lui fait confiance, la chaine de blocs devient inutile. Elle fonctionne toujours mais n'est pas l'utilisation la plus intelligente des ressources informatiques.

Or, c'est justement le cas de l'université citée plus haut. Pour valider des diplômes, on ne veut pas du pair-à-pair, bien au contraire. Il y a une autorité, l'université, et c'est elle, et elle seule, qui peut dire si j'ai une maitrise de physique ou pas (mon diplôme universitaire le plus élevé). Si on met les diplômes sur une chaine de blocs, on ne souhaite certainement pas que tout le monde puisse y écrire. Le cas est donc très différent de celui des cryptomonnaies, ou d'autres utilisations de la chaine de blocs comme la réservation de noms. Notons en outre qu'aucun détail n'est donné sur la chaine utilisée par l'université : laquelle est-ce, qui la contrôle, son logiciel est-il publié, etc. En l'absence de tous ces éléments, la chaine de blocs n'apporte aucune confiance supplémentaire. L'université aurait tout aussi bien publier les diplômes sur son site Web… (Avec quelques techniques de sécurité comme HTTPS.) Et si on veut un système commun à toutes les universités, il existe déjà.

Les défenseurs de ces utilisations inutiles de la chaine de blocs citent parfois l'argument de la signature, qui permet d'authentifier le diplôme, y compris si l'information circule et n'est pas récupérée sur le site d'origine. C'est vrai, les signatures numériques sont une très bonne idée, mais elles sont bien antérieures aux chaines de blocs et peuvent être utilisées dans ce cas (par exemple en publiant des documents signés sur le site Web). Là encore, la chaine de blocs n'apporte rien. Notez que le responsable du projet est parfaitement conscient de cette inutilité puisque, dans les commentaires à un article de NextInpact, il dit ouvertement « j'assume que le mot blockchain a un peu été un prétexte ».

Une faiblesse courante avec les chaines de blocs est que peu de gens vérifient directement sur la chaine, pourtant la seule source fiable. Ils passent en général par un système centralisé. L'entreprise privée qui a été payée par l'université le dit d'ailleurs dans les commentaires à l'article de Next Inpact déjà cité : « Techniquement, tout est fait dans BCdiploma pour que la lecture des attestations soit la plus simple possible en masquant la complexité de la blockchain. Par exemple, les certificats sont ici lus depuis le site de l’université, augmentant ainsi la confiance. » (un exemple de ce que ça donnerait). Ça annule une bonne partie de l'intérêt du projet.

Pour les mêmes raisons, tous les projets de « chaines de blocs privées » ou de « chaines à permission » sont des non-sens, en raison de leur inutilité. (À l'exception peut-être de chaines pas publiques mais pas complètement privées, entre un petit nombre d'acteurs qui ne se font qu'une confiance limitée.) Évidemment, cette inutilité n'empêche pas les projets (par exemple au niveau européen), car il y a beaucoup de consultants et d'ESN à nourrir.

Mais attention, dit la lectrice attentive de cet article : l'université peut publier des diplômes signés sur son site Web, d'accord, mais elle peut aussi arrêter de les publier. Si on fait confiance à l'université pour certifier les diplômes, mais qu'on craint qu'elle n'essaie de réécrire le passé en prétendant qu'un diplôme n'a jamais existé, la publication sur le site Web n'aide pas. Bonne remarque, et il faut donc aller plus loin que la simple publication de documents signés sur un site Web. Mais on n'a pas besoin de chaine de blocs, il suffit d'utiliser des journaux publics à ajout seul (append-only logs).

Le principe de ces journaux est de publier l'information sous une forme qui rend toute altération ultérieure détectable. Il existe plusieurs solutions pour cela, mais la plus simple conceptuellement (mais pas la plus rapide) est de numéroter chaque document publié. La suppression d'un document peut ainsi être détectée. Si on a le document 67 et qu'on ne trouve pas le 66, le problème est visible. (Ce système de numérotation des documents pour assurer l'intégrité est un très vieux système, antérieur à l'informatique, avec les journaux papier utilisés par exemple dans les commissariats. Comme le dit Wikipédia, « Il est strictement interdit de modifier ou même de raturer une inscription en main courante sous peine de la rendre caduque, c'est pourquoi les pages d'une main courante papier sont toujours numérotées. ») En dehors du monde papier, un tel système est simple à faire : l'autorité numérote les documents, les signe et les publie. Les données étant publiques, n'importe qui peut facilement vérifier leur intégrité. Un tel système est par exempe décrit dans l'article « How to time-stamp a digital document » (à télécharger ici). Un des systèmes les plus anciens à être effectivement déployé est Stamper (cf. son historique), qui publiait ses signatures (faites avec PGP)… sur Usenet.

Depuis, bien d'autres systèmes de journaux à ajout seul sont apparus et sont utilisés. L'un des plus connus est le Certificate Transparency (normalisé dans le RFC 9162). Pas du tout besoin d'une chaine de blocs pour cette tâche cruciale, puisqu'il n'y a qu'un petit nombre d'émetteurs faisant autorité (les AC).

Au passage, j'ai dit qu'un tel système permettait de détecter les modifications. Or il y a des modifications légitimes, par exemple le retrait d'un diplôme obtenu à tort. Il faut donc prévoir la possibiité qu'un document annule un précédent (mais on ne supprime pas le précédent, on le remplace). La vérification publique est contradictoire avec la possibilité d'oubli, ce qui peut être une bonne ou une mauvaise chose.


L'article seul

Des leçons à tirer du problème du .coin

Première rédaction de cet article le 20 octobre 2022


Le 18 octobre 2022, la société Unstoppable Domains a annoncé qu'elle arrêtait de vendre des noms sous .coin. Il y a des leçons intéressantes à en tirer.

Ces noms ont la syntaxe de noms de domaine mais ne « fonctionnent » pas, au sens où un nom de domaine habituel fonctionne, via une résolution par le DNS. Ils sont enregistrés, oui, dans une chaine de blocs, mais ne fonctionnent pas avec vos logiciels classiques. En pratique, ils sont très peu utilisés, ceux qui les achètent sont plutôt dans une démarche d'investissement, espérant qu'ils prendront de la valeur plus tard, plutôt que dans une démarche de présence en ligne, comme lorsqu'on achète un nom de domaine plus classique pour recevoir du courrier électronique ou afficher un site Web.

Plusieurs services analogues existent, certains commerciaux (on peut vendre des noms, des identificateurs), d'autres pas. C'est aussi un monde où on rencontre de tout, du marketing boursouflé (Web3 !), à certains vendeurs qui induisent en erreur leurs clients, par exemple en leur faisant croire que ces noms auront le même usage qu'un nom classique, utilisable via le DNS. Et il y a aussi de purs escrocs. L'intérêt, en théorie, d'enregistrer des noms dans une chaine de blocs, plutôt que via un registre traditionnel, est que la chaine fonctionne automatiquement, sans intervention humaine une fois lancée, et qu'on peut donc rester titulaire de son nom de domaine, sans risque de le voir saisi ou censuré (d'où le nom de la société Unstoppable Domains). L'idée est très ancienne (elle avait commencé avec Namecoin en 2010) mais a pris de l'ampleur ces dernières années, avec l'apparition d'intermédiaires comme Unstoppable Domains, ENS, Emercoin, etc. La vogue récente des NFT a mené certains à renommer leurs produits « NFT » mais il n'y a pas de changement de fond.

Un point important de toute cette offre est qu'il n'y a pas de coordination. Tout le monde (et son chat) peut créer une chaine de blocs ou, si on est moins ambitieux, un service de création de noms sur une chaine de blocs existante (la façon de réaliser un tel service avait été détaillée lors de la JCSA 2016). L'Internet est « sans permission » ce qui veut dire, et heureusement, qu'une innovation peut être créée et déployée sur l'Internet, sans l'autorisation de personne. Cela a permis Bitcoin (qui a créé le concept de chaine de blocs), BitTorrent mais aussi le Web (si Tim Berners-Lee avait dû patienter jusqu'à l'autorisation d'un comité, on attendrait toujours le Web). La contrepartie de cette liberté est qu'il y a aussi de mauvaises idées et des concurrences dommageables. Comme chacun (et son hamster) peut lancer un service de création et de vente de noms sur une chaine de blocs, si ces noms ont une syntaxe compatible, il y a des risques de collision. Une collision, c'est quand deux noms identiques sont enregistrés via des services différents. C'est inévitable si deux services vendent des noms avec le même suffixe.

Et c'est justement ce qui s'est produit ici : Unstoppable Domains vendait du .coin mais Emercoin le faisait également. Les collisions étaient donc inévitables. Finalement, Unstoppable Domains a décidé d'arrêter, notant qu'Emercoin était présent avant (mais qu'ils ne le savaient pas). Notons l'ironie du nom Unstoppable Domains puisque cette entreprise peut supprimer un nom à sa guise…

Ces problèmes de collision sont inévitables dès que plusieurs organisations créent des noms sans aucune coordination. C'est une des raisons pour lesquelles les racines DNS alternatives n'ont jamais décollé. Leurs promoteurs évacuent souvent le problème des collisions avec de vagues promesses « on s'arrangera ». Ici, un des deux acteurs impliqués dans la collision a décidé d'arrêter mais il n'y a aucune garantie que cela se passera toujours bien. C'est pour cela que le RFC 2826 insiste sur l'importance d'une coordination formelle. Dit autrement, il ne faut qu'un seul registre pour .coin (ou .nimporte-quoi).

J'ai dit plus haut qu'Unstoppable Domains avait supprimé les noms en .coin. En fait, c'est plus compliqué que cela. Les noms sont toujours dans la chaine de blocs (dont l'un des buts est justement d'empêcher l'effacement du passé) et Unstoppable Domains peut donc expliquer qu'en fait, ils n'ont pas supprimé les noms. Mais, comme indiqué précédemment, presque aucune application ne va regarder directement dans la chaine de blocs. Elles passent quasiment toutes par des passerelles diverses, utilisant des protocoles normalisés comme HTTP ou DNS. Ce qu'Unstoppable Domains a coupé, comme ils l'indiquent, ce sont ces passerelles. C'est en effet une malhonnêteté intellectuelle fréquente chez certains services se présentant comme pair-à-pair : le support (ici, la chaine de blocs) est bien pair-à-pair, mais en pratique presque tout le monde y accède via une passerelle qui, elle, est centralisée. Lorsque cette passerelle est fermée, vous perdez tout. (Le problème est loin d'être spécifique à Unstoppable Domains ; regardez comme les ressources IPFS, service censément pair-à-pair sont toujours annoncées sous forme d'un URL passant par une passerelle centralisée. C'est en partie dû au fait que le logiciel est très complexe à compiler et installer.)

Cette dépendance de nombreux services pair-à-pair vis-à-vis de passerelles centralisées, donc vulnérables à la censure ou à des décisions business, est un des gros problèmes, à l'heure actuelle, de beaucoup de solutions pair-à-pair.

Quelques bonnes lectures pour finir :


L'article seul

Valider du XML : exemple EPP

Première rédaction de cet article le 20 octobre 2022
Dernière mise à jour le 21 octobre 2022


Cet article n'aura probablement pas beaucoup de lecteurs car peu de gens utilisent le protocole EPP. Ce dernier sert uniquement à la communication entre registres et bureaux d'enregistrement. Il utilise le format XML, et est décrit via un langage de schéma, ce qui permet sa validation par un programme. Comme ce n'est pas tout à fait évident, je montre ici comment on peut faire cette validation.

EPP est normalisé dans le RFC 5730. Mais attention, cela ne normalise que le cœur du protocole. EPP est un protocole d'avitaillement d'objets et il peut manipuler différents types d'objets, par exemple des noms de domaine. Il faut donc ajouter un RFC par type d'objet (pour les noms de domaine, c'est le RFC 5731) et à chaque fois un nouvel espace de noms. Et EPP a diverses extensions, avec à chaque fois un espace de noms. Tout cela est formellement décrit en XML Schema. Avantage : on peut valider un document EPP automatiquement et s'assurer qu'il est conforme à ce qu'on attend, avant de le traiter.

On va utiliser ici xmllint, qu'on trouve dans tous les bons systèmes d'exploitation. xmllint exige qu'on lui passe un fichier unique pour le schéma, donc je vous ai fait un joli fichier XML Schema qui inclut toutes les extensions auxquelles j'ai pensé, epp-wrapper.xsd. Ensuite, on doit récupérer à l'IANA tous les fichiers .xsd concernant les différents schémas qu'on utilise. Cela peut se faire avec le script Python extract-xsd.py et cette boucle shell :

for schema in $(./extract-xsd.py epp-wrapper.xsd); do
    wget https://www.iana.org/assignments/xml-registry/schema/${schema}
done
  

On peut ensuite valider un document EPP. Prenons celui-ci comme exemple :


<?xml version="1.0" encoding="utf-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
  <command>
    <create>
      <domain:create
       xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
        <domain:name>example.com</domain:name>
        <domain:authInfo>
          <domain:pw>2fooBAR</domain:pw>
        </domain:authInfo>
      </domain:create>
    </create>
    <clTRID>ABC-12345</clTRID>
  </command>
</epp>

  

Cela donne :

% xmllint --noout --schema epp-wrapper.xsd test.xml
test.xml validates
  

Parfait. Un programme qui va traiter les données peut, s'il a validé, être tranquille. Il sait par exemple qu'il aura une et une seule commande dans l'élément <epp>.

Notez que c'est en écrivant cet article qu'une faille a été trouvée dans le RFC 9167.

Si maintenant on prend un document EPP invalide (ajout d'un <foobar>) :

% xmllint --noout --schema epp-wrapper.xsd test-invalid.xml
test-invalid.xml:12: element foobar: Schemas validity error : Element '{urn:ietf:params:xml:ns:epp-1.0}foobar': This element is not expected.
test-invalid.xml fails to validate
  

Parfait encore, le document invalide est rejeté.

Si on utilise une extension à EPP comme celle pour DNSSEC du RFC 5910 :


<?xml version="1.0" encoding="utf-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
  <command>
    <update>
      <domain:update
       xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
        <domain:name>example.com</domain:name>
      </domain:update>
    </update>
    <extension>
      <secDNS:create xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
        <secDNS:dsData>
          <secDNS:keyTag>12345</secDNS:keyTag>
          <secDNS:alg>3</secDNS:alg>
          <secDNS:digestType>1</secDNS:digestType>
          <secDNS:digest>49FD46E6C4B45C55D4AC</secDNS:digest>
          <!-- <secDNS:keyData>, la clé elle-même, est *facultatif* -->
        </secDNS:dsData>
      </secDNS:create>
    </extension>
    <clTRID>ABC-12345</clTRID>
  </command>
</epp>

  

La validation sera possible grâce à tous les schémas chargés :

% xmllint --noout --schema epp-wrapper.xsd test-dnssec.xml
test-dnssec.xml validates
  

L'article seul

Version 15 d'Unicode

Première rédaction de cet article le 15 septembre 2022


Le mardi 13 septembre est sortie la version 15 d'Unicode. Une description officielle des principaux changements est disponible mais voici ceux qui m'ont intéressé particulièrement. (Il n'y a pas de changement radical.)

Pour explorer plus facilement la grande base Unicode, j'utilise un programme qui la convertit en SQL et permet ensuite de faire des analyses variées. Faisons quelques requêtes SQL :

ucd=> SELECT count(*) AS Total FROM Characters;
 total  
--------
 149251

Combien de caractères sont arrivés avec la version 15 ?

ucd=> SELECT version,count(version) FROM Characters GROUP BY version ORDER BY version::float;
...
 12.0    |   554
 12.1    |     1
 13.0    |  5930
 14.0    |   838
 15.0    |  4489

4489 nouveaux caractères, c'est pas mal (la version 14 était plus calme). Quels sont ces nouveaux caractères ?

ucd=> SELECT To_U(codepoint) AS Code_point, name FROM Characters WHERE version='15.0' ORDER BY Codepoint;
 code_point |                                    name                                    
-----------+----------------------------------------------------------------------------
...
U+1D2C0    | KAKTOVIK NUMERAL ZERO
...
U+1E4D4    | NAG MUNDARI LETTER ONG
...
U+1F776    | LUNAR ECLIPSE
...
U+1F77C    | MAKEMAKE
U+1F77D    | GONGGONG
...
U+1FACF    | DONKEY

Cette version amène en effet des écritures nouvelles comme le Nag Mundari, ou les chiffres de Kaktovik.

Si vous avez les bonnes polices de caractères, vous allez pouvoir voir quelques exemples (sinon, le lien mène vers Uniview). Voici par exemple la lettre ETT du Nag Mundari 𞓩 et le chiffre de Kaktovik 2 𝋂 Il y a également de nouveaux symboles, notamment liés à l'astronomie, comme l'occultation 🝵 ou la petite planète Orcus 🝿. En plus anecdotique, on a le sans-fil 🛜, les maracas 🪇 ou l'élan 🫎. Ce dernier a d'ailleurs eu droit au point de code, U+1FACE, une utilisation amusante de l'hexadécimal.

Tiens, d'ailleurs, combien de caractères Unicode sont des symboles (il n'y a pas que les emojis parmi eux, mais Unicode n'a pas de catégorie « emoji ») :

 ucd=> SELECT count(*) FROM Characters  WHERE category IN ('Sm', 'Sc', 'Sk', 'So');
 count 
-------
  7770

Ou, en plus détaillé, et avec les noms longs des catégories :

ucd=> SELECT description,count(category) FROM Characters,Categories WHERE Categories.name = Characters.category AND category IN ('Sm', 'Sc', 'Sk', 'So') GROUP BY category, description;
   description   | count 
-----------------+-------
 Modifier_Symbol |   125
 Other_Symbol    |  6634
 Math_Symbol     |   948
 Currency_Symbol |    63

L'article seul

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 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 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

Fiche de lecture : Petite histoire du compagnonnage

Auteur(s) du livre : François Icher
Éditeur : Cairn
978-2-35068-753-7
Publié en 2019
Première rédaction de cet article le 8 septembre 2022


Vous connaissez le compagnonnage ? Probablement de nom, puisque ce système de formation et d'entraide dans le monde du travail manuel est ancien et souvent cité, par exemple lorsqu'on a parlé des réparations de Notre-Dame de Paris après l'incendie. Mais en dehors de généralités, le compagnonnage n'est pas forcément bien connu, alors qu'il a une longue histoire, résumée dans ce livre.

Le travail de l'historien sur ce sujet est à la fois facile (de nombreux documents sont disponibles) et difficile car il y a tout un folklore compagnonnique, avec ses légendes (les racines du compagnonnage au temps du roi Salomon…) et que les compagnons ne sont pas forcément ravis de voir un historien extérieur documenter leur mouvement. L'auteur se concentre évidemment sur les faits établis, qui font remonter les sources du compagnonnage, non pas au temple de Jérusalem, mais au Moyen Âge, ce qui n'est déjà pas mal. En rupture avec le système médiéval des corporations, devenu figé, voire « aristocratique », le compagnonnage est né de la volonté de certaines catégories de travailleurs manuels de s'entraider et de se former entre eux, par l'échange et le voyage (le fameux « Tour de France »). Ces compagnons ont ensuite développé un certain nombre de traditions (parfois confondues, à tort, avec celles des francs-maçons, mouvement nettement plus bourgeois) et de règles, notamment d'entraide et de fraternité.

On croit souvent qu'il n'y a qu'une sorte de compagnon mais en fait, depuis longtemps, les compagnons sont divisés entre plusieurs obédiences qui, à certaines époques, réglaient leurs divergences par la violence. Le chemin du Tour de France n'était pas un long fleuve tranquille. Les chansons de marche que les compagnons entonnaient pour se donner du courage sur la route étaient fréquemment des chants guerriers dirigés contre les autres obédiences. (Le même auteur a fait le scénario d'une BD sur Agricol Perdiguier, un compagnon du XIXe siècle resté célèbre par sa lutte incessante contre ces affrontements fratricides. Une de ses actions a été d'écrire des chansons pour les marches et les fêtes, qui ne soient pas un appel à la violence. Un feuilleton célèbre de l'ORTF avait son scénario fondé sur ces violences.) Le compagnonnage n'a évidemment jamais échappé aux problèmes politiques de son temps : catholiques stricts contre laïcs au XIXe siècle, par exemple. Mais il y avait aussi des rivalités de boutique (monopole d'accès à certains travaux) et du simple sectarisme.

Le compagnonnage a ensuite été ébranlé par la révolution industrielle, qui a semblé un moment condamner l'artisan qualifié qu'était le compagnon, par le socialisme, qui rejetait ce mouvement qui se voulait souvent élitiste, par les divers soubresauts du XXe siècle (ah, la malheureuse tentative de rénovation du compagnonnage sous le patronage de… Pétain).

Le livre décrit toutes ces aventures du mouvement compagnonnique, et son état actuel. Toujours divisé, avec trois importantes obédiences et plusieurs plus petites, le mouvement existe toujours. L'auteur explique les divergences entre ces obédiences mais je dois dire que ce n'est pas forcément facile à décrypter pour un lecteur situé à l'extérieur du mouvement compagnonnique. Ce mouvement s'est parfois adapté au monde moderne (admission des femmes à partir de 2004), mais reste également fidèle à ses origines, notamment la valorisation du travail manuel, et l'importance de la transmission du savoir.


L'article seul

Fiche de lecture : Néandertal nu

Auteur(s) du livre : Ludovic Slimak
Éditeur : Odile Jacob
978-2-7381-5723-2
Publié en 2022
Première rédaction de cet article le 1 septembre 2022


L'homme de Néandertal fait l'objet de nombreux travaux de « réhabilitation » depuis des années, le présentant comme l'égal en tout de l'Homo Sapiens moderne que nous sommes. Dans ce livre, Slimak questionne cette vision et se demande si elle n'a pas tendance à considérer l'Homo Sapiens moderne comme la référence absolue.

Ce pauvre Néandertal était, au XIXe siècle et au début du XXe, présenté comme une brute stupide, sans sentiments religieux ou artistiques, et cannibale, pendant qu'on y était. Les reconstitutions le montraient plutôt comme un singe que comme un homme. Depuis un certain temps, cette vision est contestée, et la tendance est plutôt à dire que Néandertal, s'il vivait aujourd'hui, serait notre égal en presque tout et devrait avoir le droit de vote. Trouvée sur Wikimedia Commons, une image moderne (et qui fait écho au titre du livre) : Homo_neanderthalensis_man_model.jpg.

Ludovic Slimak reprend la question dans ce court livre. Il fait d'abord remarquer que beaucoup de théories sur les humains du passé sont fragiles, reposant sur peu de données, et de datations parfois délicates. Le risque de raisonnement circulaire est important ; on trouve un certain type d'artefacts avec des squelettes d'Homo Sapiens moderne, puis on trouve des artefacts analogues en un autre endroit, sans squelettes associés, on en déduit que des Homo Sapiens les ont réalisés et finalement que Néandertal n'a rien fait. Alors qu'il était peut-être l'auteur du second jeu d'artefacts.

Mais, surtout, l'auteur s'interroge sur cette tendance à vouloir réhabiliter Néandertal en insistant sur le fait qu'il était comme nous. Est-ce que cela n'indique pas un certain refus de la différence, si Néandertal n'était acceptable que s'il était plus ou moins identique à nous ? Slimak estime qu'on n'a pas de vraies preuves que Néandertal ait eu une activité artistique. Mais est-ce que cela veut dire qu'il n'est qu'une brute sans intérêt ? L'auteur appelle à analyer ce qu'on sait de Néandertal en essayant de ne pas nous prendre comme le mètre-étalon. Par exemple, sur le cannibalisme, Slimak fait remarquer qu'il n'y a guère de doute que Néandertal était cannibable (ce qui lui a souvent été reproché), mais que certains Homo Sapiens modernes l'étaient aussi et que de toute façon ce n'est pas forcément un signe d'animalité, ce cannibalisme étant fortement ritualisé et chargé de sens divers.

Le livre est agréable à lire, avec beaucoup d'exemples concrets et de dissertations intéressantes. Je le recommande pour comprendre la complexité du débat.

Et sinon, du côté des romans, il y a des personnages intéressants de Néandertals dans le cycle de Jean Auel « Les enfants de la Terre » et, en moins documenté scientifiquement, dans la série de Jasper Fforde sur Thursday Next. Dans les deux cas, Néandertal n'est pas un singe abruti, mais il n'est pas non plus un simple clone d'Homo Sapiens.


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

Articles des différentes années : 2023  2022  2021  2020  2019  2018  2017  Précédentes années

Syndication : Flux Atom avec seulement les résumés et Flux Atom avec tout le contenu.

Un article de ce blog au hasard.