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.


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 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 9309: Robots Exclusion Protocol

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


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

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

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

User-Agent: *
Disallow: /private
  

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

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

User-Agent: GoogleBot
  

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

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

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

Disallow: /private
Allow: /private/notcompletely
  

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

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

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

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

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

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

import urllib.robotparser
import sys

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

Et, ici, on se sert de ce code :

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

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

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

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

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

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

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

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

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

Et testons-le :

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

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

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

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


Téléchargez le RFC 9309


L'article seul

RFC 9297: HTTP Datagrams and the Capsule Protocol

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Téléchargez le RFC 9297


L'article seul

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

RFC 9293: Transmission Control Protocol (TCP)

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Téléchargez le RFC 9293


L'article seul

RFC 9285: The Base45 Data Encoding

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


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

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

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


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

  

Et décodons avec le module Elixir base45 :

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

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

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

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

  

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

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


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

During handling of the above exception, another exception occurred:

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

  

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

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

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

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

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


Téléchargez le RFC 9285


L'article seul

Fiche de lecture : Le Puy du Faux

Auteur(s) du livre : Florian Besson, Pauline Ducret, Guillaume Lancereau, Mathilde Larrère
Éditeur : Les Arènes
979-10-375-0590-3
Publié en 2022
Première rédaction de cet article le 5 août 2022


Le parc d'attractions du Puy du Fou est un grand succès commercial, qui attire de nombreux visiteurs. Bon, d'accord, mais le Parc Astérix aussi, et on n'écrit pas des livres à son sujet, pourtant. Mais le Puy du Fou a une autre caractéristique : ses promoteurs prétendent qu'il est historiquement fondé, et qu'on peut non seulement s'y amuser mais en plus apprendre l'histoire. Quatre historiens se sont donc associés pour aller au parc d'attraction et vérifier.

Il y a plusieurs écueils si on veut avoir un regard critique sur l'histoire tendance Puy du Fou. Il y a le risque d'une critique élitiste, faisant fi du succès populaire du parc, méprisant la vulgarisation, et estimant que l'histoire ne doit se traiter que dans de gros livres ennuyeux pour universitaires. Il y a aussi le risque du pinaillage, pointant des erreurs de détails, par exemple sur les dates, et oubliant que le Puy du Fou a une cohérence dans sa vision de l'histoire, et que c'est elle qu'il faut étudier.

C'est ce qu'ont fait les quatre auteur·es, dont au moins deux sont activement engagés dans la vulgarisation de l'histoire, notamment sur les réseaux sociaux (les deux autres aussi, peut-être, mais je ne les connais pas). Ils sont allés au Puy du Fou, ils ont apprécié les spectacles, ils ont pris des notes et ils analysent la représentation historique. S'il y a en effet d'innombrables erreurs factuelles dans les spectacles du Puy du Fou, le plus gros problème est la vision peu historique du parc : une France éternelle, gardant son identité inchangée à travers les siècles, mélange de l'idéologie de l'Ancien Régime et du roman national du XIXe siècle. Par exemple, le spectacle se déroulant en Gaule romaine présente les seuls Gaulois comme ancêtres des Français, comme si la France d'aujourd'hui n'était pas tout autant héritière des Romains. (En prime, le spectacle montre les Gaulois chrétiens et les Romains païens, ce qui ne correspond pas à la réalité de l'époque.) Les auteur·es analysent de nombreux spectacles et la plupart (je vous laisse découvrir les exceptions) présentent la même vision d'une France mythique, qui a peu de rapport avec la réalité et avec les vrais Français des différentes époques.

Critiquer, c'est facile, mais proposer est plus difficile, pourront dire les défenseurs du parc. Mais un des chapitres les plus intéressants du livre est justement celui où les auteur·es se font scénaristes et imaginent quatre spectacles qui gardent ce qu'il y a de bien au Puy du Fou (des animations de qualité et spectaculaires, avec pyrotechnie obligatoire) tout en étant plus rigoureux historiquement. Ainsi celui de l'Antiquité montre les débats et divergences entre premiers chrétiens, alors que le Puy du Fou montre le christianisme, comme il le fait de la France, comme une entité éternelle et figée.

Faut-il aller au Puy du Fou ? Là n'est pas la question. Personnellement, je n'y suis jamais allé mais le livre peut donner envie, les spectacles semblant passionnants et bien faits. Le tout est d'y aller comme à Disneyland, pour la distraction, et pas en prétendant qu'on suit un cours d'histoire.


L'article seul

Articles des différentes années : 2022  2021  2020  2019  2018  2017  2016  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.