Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

echoping

Ève

Recherche dans ce blog :

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.


Améliorer les temps de réponse des requêtes PostgreSQL avec EXPLAIN

Première rédaction de cet article le 2 Juillet 2009


Un des pièges de SQL est que c'est un langage de haut niveau. Le rapport entre la commande qu'on tape et le travail que doit faire la machine est beaucoup moins direct et beaucoup plus dur à saisir qu'avec l'assembleur. Il est donc fréquent qu'une requête SQL qui n'aie pas l'air bien méchante prenne des heures à s'exécuter. Heureusement, PostgreSQL dispose d'une excellent commande EXPLAIN qui explique, avant même qu'on exécute la commande, ce qu'elle va faire et ce qui va prendre du temps.

Un excellent exposé d'introduction est Explaining EXPLAIN mais, uniquement avec les transparents, c'est un peu court. Voyons des exemples d'utilisation d'EXPLAIN sur la base de données de StackOverflow.

Je commence par une requête triviale, SELECT * FROM Votes et je la fais précéder de EXPLAIN pour demander à PostgreSQL de m'expliquer :

so=> EXPLAIN SELECT * FROM Votes;
                           QUERY PLAN                           
----------------------------------------------------------------
 Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=16)
(1 row)

On apprend que PostgreSQL va faire une recherche séquentielle (Seq Scan), ce qui est normal, puisqu'il faudra tout trouver, que 2379537 tuples (lignes de la table SQL) sont à examiner. Et que le coût sera entre... zéro et 36658,37. Pourquoi zéro ? Parce que PostgreSQL affiche le coût de récupérer le premier tuple (important si on traite les données au fur et à mesure, ce qui est souvent le cas en OLTP) et celui de récupérer tous les tuples. (Utiliser LIMIT ne change rien, PostgreSQL calcule les coûts comme si LIMIT n'était pas là.)

Et le 36658,37 ? C'est 36658,37 quoi ? Pommes, bananes, euros, bons points ? Ce coût est exprimé dans une unité arbitraire qui dépend notamment de la puissance de la machine. Avant de lire toute la documentation sur le calcul des coûts, faisons une autre requête :

so=> EXPLAIN SELECT count(*) FROM Votes;
                             QUERY PLAN                              
---------------------------------------------------------------------
 Aggregate  (cost=42607.21..42607.22 rows=1 width=0)
   ->  Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=0)
(2 rows)

Cette fois, le coût du premier tuple est le même que le coût total, ce qui est logique, puisque les fonctions d'agrégation comme count() nécessitent de récupérer toutes les données.

Les coûts qu'affiche EXPLAIN ne sont que des coûts estimés, sans faire effectivement tourner la requête (donc, EXPLAIN est en général très rapide). Si on veut les temps réels, on peut ajouter le mot-clé ANALYZE :

so=> EXPLAIN ANALYZE SELECT * FROM Votes;
                                                    QUERY PLAN                                                     
-------------------------------------------------------------------------------------------------------------------
 Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=16) (actual time=0.042..3036.302 rows=2379537 loops=1)
 Total runtime: 5973.786 ms
(2 rows)

Cette fois, on a le temps effectif, trois secondes. On peut en déduire que, sur cette machine, l'unité de coût de PostgreSQL vaut environ 0,08 milli-secondes (3036,302 / 36658,37). Sur un serveur identique, mais avec des disques de modèle différent et un autre système de fichiers, on arrive à une valeur quasiment identique. Mais sur un simple portable, je mesure un facteur de 0,5 milli-secondes par unité de coût.

De toute façon, il faut faire attention : ce facteur d'échelle dépend de beaucoup de choses, à commencer par les réglages du SGBD. Est-ce que ce ratio de 0,08 milli-secondes peut nous aider à prévoir le temps d'exécution d'une requête ? Voyons une requête plus compliquée qui compte le nombre de votes ayant eu lieu dans les 24 heures ayant suivi le message :

so=> EXPLAIN SELECT count(votes.id) FROM votes, posts WHERE votes.post = posts.id AND 
so->                             votes.creation <= posts.creation + interval '1 day';
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Aggregate  (cost=147911.94..147911.95 rows=1 width=4)
   ->  Hash Join  (cost=31161.46..145928.99 rows=793179 width=4)
         Hash Cond: (votes.post = posts.id)
         Join Filter: (votes.creation <= (posts.creation + '1 day'::interval))
         ->  Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=12)
         ->  Hash  (cost=15834.65..15834.65 rows=881665 width=12)
               ->  Seq Scan on posts  (cost=0.00..15834.65 rows=881665 width=12)
(7 rows)

PostgreSQL affiche un coût de 147911,95 soit 11,8 secondes. Et le résultat effectif est :

so=> EXPLAIN ANALYZE SELECT count(votes.id) FROM votes, posts WHERE votes.post = posts.id AND 
                            votes.creation <= posts.creation + interval '1 day';
                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=147911.94..147911.95 rows=1 width=4) (actual time=15125.455..15125.456 rows=1 loops=1)
...

soit 15 secondes au lieu des 11 prévues, ce qui n'est pas trop mal, pour une fonction de coût qui agrège beaucoup de choses différentes.

Que voit-on d'autre sur la sortie de EXPLAIN pour cette requête ? Que les coûts s'additionnent (regardez bien l'indentation des lignes commençant par -> pour voir comment faire l'addition). La recherche séquentielle la plus interne (sur la table Posts) coûte 15834,65, le hachage coûte le même prix (il n'a rien ajouté), l'autre recherche séquentielle (sur la table Votes) a un coût de 36658,37 et ces deux coûts vont s'ajouter, ainsi que le filtrage et le test de jointure, pour donner 145928,99, le coût de la jointure. Ensuite, un petit coût supplémentaire restera à payer pour l'agrégat (count()) nous emmenant à 147911,95.

L'addition des coûts se voit encore mieux sur cette requête :

so=> EXPLAIN SELECT count(votes.id)*100/(SELECT count(votes.id) FROM Posts, Votes 
so(>                                 WHERE Votes.post = Posts.id) AS percent
so->        FROM votes, posts WHERE 
so->                    votes.post = posts.id AND 
so->                    votes.creation > (posts.creation + interval '1 day')::date;
                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 Aggregate  (cost=287472.95..287472.97 rows=1 width=4)
   InitPlan
     ->  Aggregate  (cost=133612.15..133612.16 rows=1 width=4)
           ->  Hash Join  (cost=30300.46..127663.31 rows=2379537 width=4)
                 Hash Cond: (public.votes.post = public.posts.id)
                 ->  Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=8)
                 ->  Hash  (cost=15834.65..15834.65 rows=881665 width=4)
                       ->  Seq Scan on posts  (cost=0.00..15834.65 rows=881665 width=4)
   ->  Hash Join  (cost=31161.46..151877.84 rows=793179 width=4)
         Hash Cond: (public.votes.post = public.posts.id)
         Join Filter: (public.votes.creation > ((public.posts.creation + '1 day'::interval))::date)
         ->  Seq Scan on votes  (cost=0.00..36658.37 rows=2379537 width=12)
         ->  Hash  (cost=15834.65..15834.65 rows=881665 width=12)
               ->  Seq Scan on posts  (cost=0.00..15834.65 rows=881665 width=12)
(14 rows)

dont le temps d'exécution effectif est de 30 secondes, très proche des 23 secondes calculées en multipliant le coût total de 287472,97 par le facteur d'échelle de 0,08 millisecondes.

Plusieurs essais avec des bases très différentes montrent que le facteur d'échelle (la valeur en milli-secondes d'une unité de coût) est plutôt stable. Voici un résultat avec la base de DNSmezzo :

dnsmezzo2=> EXPLAIN ANALYZE SELECT DISTINCT substr(registered_domain,1,43) AS domain,count(registered_domain) AS num FROM DNS_packets WHERE file=3 AND NOT query AND rcode= 3 GROUP BY registered_domain ORDER BY count(registered_domain) DESC;
                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Unique  (cost=496460.23..496460.31 rows=10 width=12) (actual time=41233.482..42472.861 rows=234779 loops=1)
   ->  Sort  (cost=496460.23..496460.26 rows=10 width=12) (actual time=41233.478..41867.259 rows=234780 loops=1)
         Sort Key: (count(registered_domain)), (substr(registered_domain, 1, 43))
         Sort Method:  external merge  Disk: 11352kB
         ->  HashAggregate  (cost=496459.91..496460.06 rows=10 width=12) (actual time=38954.395..39462.324 rows=234780 loops=1)
               ->  Bitmap Heap Scan on dns_packets  (cost=197782.55..496245.34 rows=42914 width=12) (actual time=34594.268..38306.833 rows=250006 loops=1)
                     Recheck Cond: ((rcode = 3) AND (file = 3))
                     Filter: (NOT query)
                     ->  BitmapAnd  (cost=197782.55..197782.55 rows=87519 width=0) (actual time=34590.468..34590.468 rows=0 loops=1)
                           ->  Bitmap Index Scan on rcode_idx  (cost=0.00..74228.75 rows=1250275 width=0) (actual time=28144.654..28144.654 rows=1290694 loops=1)
                                 Index Cond: (rcode = 3)
                           ->  Bitmap Index Scan on pcap_idx  (cost=0.00..123532.10 rows=2083791 width=0) (actual time=6393.187..6393.187 rows=2233781 loops=1)
                                 Index Cond: (file = 3)
 Total runtime: 42788.863 ms
(14 rows)

L'article seul

RFC 1498: On the Naming and Binding of Network Destinations

Date de publication du RFC : Août 1993
Auteur(s) du RFC : Jerome H. Saltzer (MIT, Laboratory for Computer Science)
Statut inconnu, probablement trop ancien
Première rédaction de cet article le 2 Juillet 2009


Les discussions d'architecture des réseaux à l'IETF ou dans d'autres communautés techniques achoppent souvent sur le vocabulaire. Qu'est-ce que le nom d'une machine ? Qu'identifie t-il réellement ? La clé publique SSH d'une machine est-elle un nom ? D'ailleurs, faut-il identifier les machines ? Pourquoi pas les processus ? Ou les utilisateurs ? Et l'adresse ? Quel est son statut ? http://www.foo.example:8080/toto est-il une adresse bien qu'il inclus un nom ? Il ne s'agit pas uniquement de pinaillage philosophique. C'est en partie à cause du flou sur les concepts que les discussions d'architecture tournent en rond et s'éternisent.

D'innombrables documents, dont ce RFC, ont été produits pour tenter de clarifier la situation. Mais aucune terminologie cohérente n'a été adoptée. Notre RFC 1498 est donc une étape, une des plus souvent citées, dans cette longue liste. Quels sont ses points essentiels ? (Attention si vous distribuez ce document, c'est un des rares RFC qui a une licence qui n'autorise pas une distribution libre.)

Le RFC essaie d'appliquer les principes des systèmes d'exploitation aux réseaux. Les systèmes d'exploitation, eux aussi, doivent identifier les fichiers, les utilisateurs, les disques, les partitions, les parties de la mémoire, etc. Le RFC reprend la terminologie classique (mais très peu appliquée en pratique) de John Shoch, que tous les étudiants des années 1980 et 1990 ont apprise par cœur (Shoch, « A note on Inter-Network Naming, Addressing, and Routing », une copie est disponible sous le nom d'IEN 19 ) :

  • Un nom identifie ce qu'on veut,
  • Une adresse identifie où cela se trouve,
  • Une route identifie comment y aller.

À noter que cette terminologie, dans notre RFC, ne ne fait pas référence à la forme des identificateurs. Ainsi, un nom peut aussi bien être une suite de lettres ayant un sens qu'une série de chiffres opaques (contrairement à une vision trop simpliste selon laquelle les noms sont juste une forme conviviale des adresses). La terminologie de ce RFC ne sépare donc pas les identificateurs qu'un humain peut traiter et ceux qu'une machine peut traiter.

On peut voir tout de suite (ce point ne figure pas dans le RFC) que cette terminologie ne s'applique pas du tout à l'Internet. Une adresse IP, par exemple, n'identifie pas une position dans la topologie de l'Internet, puisqu'il existe des adresses PI, il y a l'anycast, etc. Un URL est un mélange de nom (la ressource que je veux) et d'adresse (puisqu'il indique où trouver la ressource, avec pas mal de détails de bas niveau comme le numéro de port).

J. Saltzer, donc, procède autrement. Il distingue quatre types d'objets qu'on peut avoir envie d'identifier :

  • Les services et les utilisateurs. Les services sont, par exemple, un service de synchronisation d'horloge.
  • Les nœuds du réseau (en gros, les machines).
  • Les points d'attachement au réseau. Ce sont les endroits où une machine se connecte et, dans la terminologie de Shoch, ce sont les adresses. Une machine IP multihomée aura ainsi plusieurs points d'attachement au réseau.
  • Les chemins (ou routes) entre points d'attachement.

Et Saltzer insiste sur le fait qu'un nom peut prendre plusieurs formes (par exemple une binaire sur le câble et une lisible pour les documentations et les fichiers de configuration), sa nature ne dépend pas de sa forme.

Là encore, même si ce point n'est pas abordé dans le RFC, on peut s'amuser à chercher une correspondance entre ces types et les identificateurs utilisés sur l'Internet :

  • Les services ou utilisateurs sont typiquement identifiés par un URI (ou parfois par une forme dégénérée, uniquement le nom de domaine, comme lorsqu'on dit fr.wikipedia.org au lieu de http://fr.wikipedia.org/),
  • Les nœuds n'ont pas vraiment d'identificateurs, à part dans certains protocoles comme SSH et HIP,
  • Les points d'attachement sont vaguement identifiés par les adresses IP mais des techniques comme l'anycast ou des mécanismes d'allocation comme les adresses PI brouillent sérieusement les choses (avec l'anycast, l'adresse IP est plutôt un identificateur de service),
  • Les chemins sont la liste des adresses IP des routeurs traversés, celle qu'affiche traceroute.

Pour notre RFC, les quatre types d'objets cités ci-dessus ont chacun une identité. Cette identité se maintient même si l'objet change ses liaisons avec les autres objets. Ainsi, un service ne devrait pas changer de nom lorsqu'il migre d'une machine à une autre, une machine (un nœud) ne devrait pas changer de nom lorsqu'il migre d'un point d'attachement à un autre, etc.

L'essentiel, donc, dit le RFC, est la liaison (binding) qui est faite entre un type d'objets et un autre. Ainsi, il doit exister un mécanisme de découverte qui permet de faire la liaison entre un service et la machine sur laquelle il opère. Un autre mécanisme de liaison permet de passer de la machine au point d'attachement. Un dernier fait passer du point d'attachement au chemin. Chacun de ces mécanismes doit également effectuer un choix, car il existe souvent plusieurs réponses.

Dans l'Internet actuel, en l'absence de vrai identificateur de machine, il y a plutôt un mécanisme unique, le DNS, qui sert aux deux premiers rôles. BGP, lui, prend en charge le dernier. (Ils ne sont pas cités dans le RFC.)

Il existe aussi parfois un autre mécanisme préalable aux autres, celui qui permet de trouver l'identificateur du service, c'est par exemple un moteur de recherche. La question est discutée dans le RFC, qui note qu'il n'y a pas toujours une liaison explicite, dans une table, entre l'idée humaine d'un service et son nom formel. Ainsi, changer un nom formel est très difficile (voir l'excellent article Cool URIs don't change).

Le RFC discute ensuite quelques cas de réseaux réels, en tenant de les mettre en correspondance avec les notions de types d'objets et de liaisons. Dans le cas d'Ethernet, l'adresse MAC est très mal nommée puisqu'elle est constante et ne dépend pas de la localisation. C'est en fait plutôt un nom et elle est utilisée comme telle par certains logiciels, par exemple pour le contrôle de la licence d'utilisation. Le cas est en fait plus complexe, par exemple parce qu'une machine a deux cartes Ethernet et peut se connecter à un réseau Ethernet en deux points. Les réseaux réels n'ont pas été conçus selon une architecture propre...

Un autre exemple est le vieux NCP où l'identificateur appelé « nom » était en fait dépendant de la position de la machine. En l'absence de mécanisme de résolution commun, la liaison entre un nom et un point d'attachement était câblée en dur dans toutes les machines du réseau. Changer une machine de place imposait donc de changer son nom... ou de mettre à jour toutes les machines du réseau, ce qui n'est pas mieux. Saltzer argumente donc que la nature d'un identificateur dépend de l'existence ou nom d'un mécanisme de résolution, et donc de l'existence d'une vraie liaison avec les identificateurs des autres types d'objet (Un autre exemple aurait pu être Fidonet.)

Donc, pour résumer ce brillant document :

  • Il existe plusieurs types d'objet à identifier,
  • Un mécanisme de liaison est nécessaire pour passer des identificateurs d'un type aux identificateurs d'un autre type,
  • Les réseaux réels comme l'Internet ne collent jamais parfaitement aux modèles.

Téléchargez le RFC 1498


L'article seul

RFC 1122: Requirements for Internet Hosts - Communication Layers

Date de publication du RFC : Octobre 1989
Auteur(s) du RFC : Robert Braden (University of Southern California (USC)/ Information Sciences Institute (ISI))
Chemin des normes
Première rédaction de cet article le 2 Juillet 2009


Compagnon du RFC 1123, ce document normalise ce que toute machine terminale (host, par opposition à routeur) connectée à l'Internet devrait savoir. Le RFC 1123 fixait les règles des « couches hautes » (notamment la couche application), notre RFC s'attaque aux « couches basses », notamment réseau et transport).

Le résultat est un document épais (116 pages), décrivant en détail tout ce qu'IP et TCP doivent faire sur une machine terminale (les routeurs sont, eux, traités dans le RFC 1812). La section 1.4 note que les discussions sur ce RFC avaient duré dix-huit mois et généré trois méga-octets de courrier, ce qui était considérable à l'époque mais est moins qu'un document MS-Word d'une page d'aujourd'hui.

Ce RFC n'avait pas pour but de changer les normes existantes (comme le RFC 791 pour IPv4 ou RFC 793 pour TCP) mais de les résumer et les préciser. Comme le note la section 1, « une mise en œuvre de bonne foi des protocoles TCP/IP, faite en lisant les normes, ne doit différer de ce RFC 1122 que par des détails ».

Aujourd'hui, plusieurs de ses sections sont bien dépassées mais il reste la norme officielle, personne n'a eu le courage de mettre ce travail à jour.

Il n'est donc pas possible de résumer tout le RFC et j'ai donc sélectionné arbitrairement quelques points qui me semblaient intéressants.

La section 1.1 rappelle l'architecture de l'Internet et la section 1.1.1 revient sur la notion de machine terminale (host dans le RFC). La section 1.1.2 note notamment que :

  • L'Internet est un réseau de réseaux. Une machine terminale ne se connecte donc pas réellement à l'Internet, elle se connecte à un réseau connecté à Internet. Cette connexion se fait avec les mêmes protocoles, qu'on communique avec des machines du même réseau ou bien avec des machines lointaines. (La section 1.1.3 en donne une liste partielle, du protocole IPv4 pour les couches basses aux protocoles d'application comme telnet ou SMTP.)
  • Les routeurs (le RFC utilise encore souvent le vieux terme de « passerelle » - gateway - qui n'est plus utilisé aujourd'hui que pour les engins travaillant dans la couche 7) n'ont pas de mémoire, ils routent chaque paquet indépendamment des autres. Les machines terminales ont donc à faire tout le travail d'établissement et de maintien des connexions.
  • Le routage, par contre, ne doit être fait que par les routeurs. Il doit y avoir une stricte séparation entre machines terminales et routeurs. (Certains systèmes d'exploitation comme les Unix BSD routaient autrefois automatiquement dès qu'ils étaient connectés à deux réseaux. La section 1.1.4 explique pourquoi c'est une mauvaise idée. Voir aussi la discussion à la fin de la 3.3.4.2)

La section 1.2 pose les grands principes comme le célébrissime Principe de Robustesse (section 1.2.2), « Soyez tolérant pour ce que vous recevez et exigeant pour ce que vous envoyez », principe qu'on trouve dans plusieurs autres RFC. Ainsi, un programmeur doit résister à la tentation d'exploiter les cas spécifiques d'une norme, pour éviter de perturber les autres implémentations.

La section 1.2.4 détaille la configuration des machines terminales. Elle se faisait entièrement à la main à l'époque. Aujourd'hui, avec la disponibilité fréquente de DHCP (RFC 2131), il vaut mieux oublier cette section et lire le dernier document sur la configuration, le RFC 5505.

Ensuite, le RFC suit le modèle en couches (section 1.3.1), ainsi que le vocabulaire spécifique de chaque couche pour désigner une unité de transmission de données sur le réseau (frame pour la couche 2, packet ou datagram - selon que la fragmentation aie eu lieu ou pas - pour la couche 3, messagesegment pour TCP - pour la couche 4).

La section 2 concerne donc la couche de même rang. C'est ici que se trouve ARP (section 2.3.2, qui insiste que l'expiration des entrées du cache ARP est obligatoire, ce qui n'était pas assez clair dans le RFC 826). C'est aussi dans cette section (en 2.3.3) que l'on rappelle que l'encapsulation normale des paquets IP sur Ethernet est celle du RFC 894, où le troisième champ de l'en-tête Ethernet est le type du protocole de couche 3 (0x800 pour IPv4) pas celle, bien plus complexe, du RFC 1042, où la longueur de la trame se trouve à cet endroit.

Évidemment, la section 3, consacrée à la couche équivalente, est bien plus longue. Elle porte aussi son âge, par exemple en consacrant une section 3.2.1.3 aux classes d'adresse, supprimées depuis.

Parmi les nombreux points abordés, le choix du TTL à mettre dans les paquets (section 3.2.1.7). Une machine terminale ne doit pas émettre un paquet avec un TTL déjà à zéro. La mise en œuvre d'IP doit permettre aux applications de fixer le TTL (sur une machine Posix, cela se fait normalement avec setsockopt(), voir « Tester quels bits de l'en-tête IP on peut changer »).

Mais il y a aussi des exigences du RFC qui ne sont plus du tout suivies aujourd'hui. La section 3.2.1.8 obligeait toute mise en œuvre d'IP à permettre le routage par la source alors que, pour des raisons de sécurité, quasiment plus aucun routeur ne l'accepte.

ICMP (RFC 792) faisant conceptuellement partie de la couche 3, il a droit à une sous-section de la section 3. Elle rappelle par exemple qu'un paquet ICMP d'erreur ne doit jamais être envoyé lorsque le paquet original était lui-même un paquet ICMP d'erreur, pour éviter les boucles. (On lit souvent cette règle énoncée comme « Un paquet ICMP ne doit jamais être envoyé en réponse à un paquet ICMP », ce qui est complètement faux, voir par exemple ping avec les ICMP echo, revus en section 3.2.2.6.)

Parmi les autres sujets abordés, la détection d'un routeur mort, qui ne transmet plus les paquets. Comme les machines terminales n'ont pas (ou plus) de protocole de routage (comme, par exemple, RIP), comment savent-elles que le routeur, en panne, est devenu un trou noir ? La méthode recommandée est, formellement, une violation du modèle en couches, mais c'est la seule qui marche dans tous les cas : les couches hautes doivent informer IP qu'elles ne reçoivent plus rien. (La section 3.3.1.4 est une passionnante discussion des autres options possibles. À lire par tout ingénieur qui s'intéresse aux réseaux.)

Le multihoming, la connexion d'une machine (ou d'un réseau) à plusieurs réseaux, a toujours été un problème difficile pour IP. La section 3.3.4 tente de fixer certaines règles. Par exemple, lors d'une réponse, l'adresse IP source de la réponse doit être l'adresse IP de destination où a été envoyée la question. Ou encore, une application cliente doit avoir la possibilité de choisir son adresse IP source (directive BindAddress de OpenSSH ou bien tcp_outgoing_address de Squid). Et que faire en l'absence de telles directives ? C'est l'objet de la section 3.3.4.3 qui demande de privilégier, si possible, une adresse source située dans le même réseau physique que l'adresse de destination (notez que le RFC 3684 a depuis fourni une solution plus générale à cette question de sélection d'adresse source, pour le protocole IPv6). Par exemple, une machine choisira comme adresse IP source 127.0.0.1 si elle doit contacter localhost et son adresse globale si elle doit contacter une autre machine.

Au sommet de la couche 3 se trouve l'interface avec la couche 4. La section 3.4 détaille les services que doit rendre cette interface. Par exemple, la couche 3 doit fournir un moyen de fixer (pour un paquet émis) et d'interroger (pour un paquet reçu) des paramètres comme le TTL ou le TOS (depuis rebaptisé DSCP par le RFC 2474). Cette section est rédigée sous forme d'une API virtuelle (les API de programmation réseau réelles ont suivi un autre modèle, mais fournissent les mêmes services, cf. le livre de Stevens). Cette API comporte des méthodes comme SEND(src, dst, prot, TOS, TTL, BufPTR, len, Id, DF, opt) (envoi d'un paquet en fixant chaque paramètre).

Finalement, un tableau synthétique, en section 3.5, résume tout ce que doit implémenter le programmeur qui réalise une mise en œuvre d'IP.

La couche 4 est ensuite traitée dans la section 4. Suivant le modèle en sablier, une machine connectée à Internet a un grand choix de couches 2 et de supports physiques, une seule couche 3, IP, et à nouveau un choix en couche 4. À l'époque de notre RFC, il n'y avait qu'UDP (RFC 768) et TCP (RFC 793). Depuis, plusieurs autres ont rejoint ces deux pionniers.

Parmi les exigences pour UDP, le RFC cite une forte recommandation d'activer la somme de contrôle (section 4.1.3.4). Celle-ci est en effet facultative en UDP et plusieurs protocoles ont souffert d'avoir cru qu'ils pouvaient s'en passer (notamment NFS et le DNS pour lequel la version 1 de Zonecheck testait que cette somme de contrôle était bien activée).

TCP est bien plus riche et fait donc l'option d'une section nettement plus longue, la 4.2. À l'époque comme aujourd'hui, il est le protocole de transport le plus utilisé.

Le RFC commence (section 4.2.2) par une discussions sur le rôle des ports, en rappelant que la seule distinction normalisée entre les ports est celle entre ceux inférieurs à 255 (services normalisés) et les autres. La distinction entre les ports privilégiés (inférieurs à 1024) et les autres n'est pas normalisée, en pratique, elle est spécifique à Unix. Le RFC ne s'oppose pas à cette distinction mais note à juste titre qu'elle ne vaut pas grand'chose en matière de sécurité puisque rien ne dit que la machine avec qui on correspond applique les mêmes règles. En outre, depuis la publication du RFC 1122, la vaste diffusion des ordinateurs fait qu'un éventuel attaquant a probablement sa propre machine, sur laquelle il est root. La très relative protection sur laquelle comptaient rlogin (en vérifiant que le port source était 513) et le DNS (qui utilisait à une époque lointaine un port source fixe, 53), ne vaut donc plus rien.

Il y a plein d'autres détails intéressants à couvrir comme l'option PUSH (section 4.2.2.2) dont le RFC rappelle qu'elle ne fournit pas un marquer de fin de message mais qu'elle indique juste un souhait que les données soient transmises « le plus vite possible » (TCP attend normalement un peu pour voir si d'autres données n'arrivent pas, cette optimisation, l'algorithme de Nagle est à nouveau discutée en section 4.2.3.4).

Finalement, un tableau synthétique, en section 4.2.5, résume tout ce que doit implémenter le programmeur qui réalise une mise en œuvre de TCP.


Téléchargez le RFC 1122


L'article seul

La version 10 de BIND avance

Première rédaction de cet article le 30 Juin 2009


La prochaine version de BIND, le serveur de noms le plus répandu sur l'Internet, est désormais en phase active de développement, financée par des organisations comme l'AFNIC. Cette version sera marquée par plusieurs changements majeurs, notamment un mode de développement nettement plus ouvert (BIND 9 avait toujours été géré de manière très privée).

Une liste résumée des principaux changements est en ligne. La nouvelle version sera écrite en C++, avec Python pour les extensions et les scripts. Il existe aussi déjà un Trac public, en https://bind10.isc.org/. Pour l'instant, il contient surtout du remue-méninges. Et il existe une liste de diffusion des développeurs et personnes intéressées.


L'article seul

Pourquoi le DNS, qu'est-ce qu'il apporte par rapport aux adresses IP ?

Première rédaction de cet article le 30 Juin 2009


Le DNS est souvent présenté comme utile, car permettant d'utiliser des identificateurs « conviviaux » (les noms de domaine) plutôt que des suites de chiffres (les adresses IP). Cette explication n'est pas fausse mais elle est très incomplète.

On trouve une justification du DNS analogue à celle-ci en beaucoup d'endroits. L'article de Wikipédia dit « Il n'est pas évident pour un humain de retenir ce numéro lorsque l'on désire accéder à un ordinateur d'Internet. C'est pourquoi un mécanisme a été mis en place pour permettre d'associer à une adresse IP un nom intelligible, humainement plus simple à retenir, appelé nom de domaine.  ». Sur le site de l'AFNIC, on trouve « Sans le DNS, il faudrait mémoriser l'adresse d'un site ou une adresse électronique sous la forme de l'adresse IP du domaine (qui est une suite de chiffres. Exemple : mon-correspondant@[192.134.4.35]) ! ». Et, en anglais, on voit sur le site de l'ICANN « Because IP addresses (which are strings of numbers) are hard to remember, the DNS allows a familiar string of letters (the "domain name") to be used instead. So rather than typing "192.0.34.163," you can type "www.icann.org." ». Mais ce n'est pas parce que tout le monde répète quelque chose que c'est complètement vrai. D'abord, le fait que des identificateurs composés uniquement de chiffres soient moins pratiques n'est pas si évident que cela. Ainsi, les numéros de téléphone restent largement utilisés et n'ont (malheureusement) pas été remplacés par les URL SIP comme sip:helpdesk@voip.apnic.net. (Curiosité : il existe même une proposition de remplacer les noms de domaine par des numéros de téléphone, ENUM.)

Personnellement, je pense que les noms de domaine sont plus simples, car plus mémorisables et plus porteurs d'identité mais, manifestement, tout le monde n'est pas d'accord, sinon les numéros de téléphone auraient disparu depuis longtemps (supprimant également le problème de la portabilité).

Les noms « conviviaux » ont un autre problème : comme ils ont une sémantique, ils font l'objet de conflits. Tout le monde veut sex.com et la multinationale Kraft Foods peut empêcher Mme Milka de garder son nom de domaine s'il correspond à une marque déposée de ladite multinationale.

Néanmoins, le DNS est le système d'identificateur le plus fréquent sur l'Internet. Quasiment toutes les transactions sur le réseau passent par lui, et le trafic sur les serveurs de noms ne cesse pas d'augmenter. C'est parce que les noms de domaine fournissent un service bien plus utile que la « convivialité » des noms : ils fournissent de la stabilité. La plupart des sites Internet reçoivent des adresses IP liées au fournisseur d'accès ou à un hébergeur. Ce sont des adresses PA, dont il faut changer si on change de prestataire. Si je fait de la publicité pour mon blog en annonçant http://208.75.84.80/, son adresse IP actuelle, ce n'est pas seulement « moins joli » et « moins mémorisable » que http://www.bortzmeyer.org/, c'est surtout moins stable. L'adresse IP 208.75.84.80 est liée à l'hébergeur actuel, Slicehost, et devra changer si je passe à un autre hébergeur comme OVH. Et je devrais prévenir tous mes lecteurs, faire corriger tous les articles qui parlaient de ce blog, demander à Google de changer l'URL (tout en gardant mon PageRank), etc.

Bien sûr, il existe des adresses IP indépendantes du fournisseur, les adresses PI. Mais elles ne sont pas forcément faciles à obtenir et, surtout, elles ne résolvent pas complètement la question de la stabilité. Si deux services sont sur la même machine, et ont donc la même adresse IP, et que j'en déménage un seul, il devra bien changer d'adresse, même si l'ancienne et la nouvelle étaient indépendantes du prestataire. En application du principe « Cool URIs don't change », les noms de domaine permettent la permanence des identificateurs.

Et ne parlons même pas de la transition d'adresses IPv4 aux adresses IPv6. Sans l'intermédiaire des noms de domaine, cette transition serait encore plus pénible qu'elle ne l'est.

(Certaines personnes relativisent l'importance de la stabilité des URL en affirmant que les moteurs de recherche permettent de toute façon de retrouver la page Web. D'abord, les noms de domaine ne servent pas qu'au Web. Ensuite, le moteur de recherche ne permet justement aucune stabilité : un jour, taper « hôtel luchon » dans Google mènera à l'hôtel Majestic, un jour à un autre hôtel...)


L'article seul

strlen et l'optimisation

Première rédaction de cet article le 25 Juin 2009
Dernière mise à jour le 30 Juin 2009


Comme le savent les lecteurs de ce blog, je suis en général très méfiant vis-à-vis de l'optimisation prématurée des programmes. Pas seulement parce que la plupart des programmeurs qui parlent de performance tout de suite ne mesurent pas le résultat de leurs bricolages mais aussi parce que cette « optimisation » est souvent un prétexte pour faire du code imbittable et inmaintenable. Mais, comme avec toute règle, il y a des exceptions.

Par exemple, si une fonction donnée est appelée très souvent, elle est évidemment une bonne candidate pour des optimisations très poussées. C'est le cas de strlen, fonction de la libc qui mesure la longueur d'une chaîne de caractères en C. Les programmes écrits en C (ce qui inclue les implémentations des langages comme Ruby ou Perl) appellent strlen tout le temps. On peut donc, une fois une mise en œuvre correcte de strlen produite, chercher des optimisations de bas niveau.

Il est par exemple amusant de comparer les deux versions produites par OpenBSD :

La GNU libc dispose également d'une telle version optimisée sur les i386 (version en assembleur pour i586 et plus). Voir aussi un bon article sur l'écriture en assembleur de strlen. Évidemment, strcmp serait encore plus intéressante à étudier, car plus complexe (les deux chaînes n'ont pas forcément la même longueur).

Une autre curiosité pour les amateurs d'optimisation est la discussion sur un strlen() rapide pour UTF-8.

Merci à Victor Stinner pour ses intéressants ajouts.


L'article seul

Tutoriel DNSSEC à la conférence SARSSI

Première rédaction de cet article le 22 Juin 2009


À la conférence SARSSI à Luchon, j'ai eu le plaisir de faire un tutoriel sur DNSSEC.

SARSSI est une petite conférence scientifique qui se déroule dans un cadre magnifique (surtout qu'il fait beau). Les sessions ont lieu dans le superbe « Pavillon Normand » qui avait été construit à Paris pour une Exposition Universelle et démonté ensuite pour être remonté à Luchon.

Les transparents du tutoriel DNSSEC sont le résultat du travail de plusieurs ingénieurs successifs à l'AFNIC. Voici leur source au format OpenOffice : dnssec-tutorial-sarssi.odp.


L'article seul

Fiche de lecture : Freakonomics

Auteur(s) du livre: Steven Levitt, Stephen Dubner
Éditeur: Penguin Books
978-0-14-103008-1
Publié en 2005
Première rédaction de cet article le 22 Juin 2009


Ce livre a déjà fait l'objet de plein de mentions sur beaucoup de blogs différents donc je n'étais pas sûr qu'il fallait ajouter le mien. Mais, bon, le livre m'a énormément intéressé donc j'avais envie, d'une part d'en recommander la lecture, d'autre part d'ajouter quelques bémols.

Steven Levitt est économiste. Comme beaucoup d'économistes, il voit le monde uniquement à travers des lunettes d'économiste et analyse tous les comportements humains en terme de calculs rationnels visant à maximiser les gains matériels. Contrairement à beaucoup d'économistes, il ne fait pas trop de mathématiques et il ne s'intéresse guère aux politiques financières des banques centrales ou à la politique fiscale de l'État. Non, il se penche plutôt sur des sujets peu ou pas étudiés par l'économie et parfois pas étudiés du tout. Ses exemples pittoresques ont déjà fait la joie de beaucoup de commentateurs et facilité la tâche de l'éditeur qui cherche des choses amusantes à mettre sur le quatrième de couverture : les enseignants de Chicago et les joueurs de sumo trichent-ils ? Pourquoi la baisse de la criminalité aux États-Unis au début des années 1990 ? Peut-on déterminer l'efficacité de l'éducation que les parents donnent à leurs enfants ?

Bien que Levitt joue les modestes en prétendant qu'il est nul en maths, une bonne partie de ses méthodes emprunte aux statistiques. Par exemple, aussi bien pour les matchs de sumo que pour les examens scolaires à Chicago, il peut démontrer la triche uniquement en regardant les données, sans enquêter sur le terrain. (On comprend pourquoi tant d'organismes refusent de distribuer leurs données brutes sous une forme numérique : cela révèle plein de choses.) Pas besoin de parler japonais, mais il faut bien connaitre les règles de ce jeu. Si deux joueurs de sumo à peu près de force égale (à en juger par leurs matches précédents) se rencontrent, la probabilité de victoire de chacun est d'à peu près 50 %. Or, si le match en question est décisif pour l'un mais pas pour l'autre (par exemple parce qu'il est déjà qualifié), celui pour lequel le match est décisif gagne nettement plus souvent que ne le voudrait le calcul des probabilités. Est-ce simplement parce qu'il est plus motivé, à cause de l'importance de l'enjeu ? Mais, aux rencontres suivantes des deux mêmes adversaires, le joueur qui a gagné perd nettement plus souvent qu'il ne le devrait. Difficile de penser qu'il n'y a pas eu accord et renvoi d'ascenseur...

Même chose pour les enseignants de Chicago. Depuis qu'un PHB local s'est mis en tête d'évaluer les enseignants sur les résultats de leurs élèves, la motivation pour tricher, qui n'existait que pour les cancres, s'est étendue à leurs professeurs. Par une excellente analyse statistique, Levitt montre la réalité de cette fraude. À noter qu'il ne se demande pas une seconde si la décision d'augmenter le salaire des profs en fonction des notes des élèves n'était pas, peut-être, une stupide idée. Il se contente de laisser entendre que, si on ne licencie pas davantage de tricheurs, c'est à cause des syndicats trop puissants.

Les tricheurs ne sont en effet pas très forts en statistiques : les plus stupides changent toujours les réponses des mêmes questions. Les plus malins essaient de changer des questions au hasard mais l'être humain est un mauvais générateur de nombres aléatoires et l'impitoyable statistique montre facilement le côté invraisemblable de certaines notes. (Levitt revient sur la difficulté que nous avons à comprendre l'aléatoire en étudiant les scores d'une équipe de base-ball, les Kansas City Royals, et leur longue série de matches perdus en montrant que, si peu intuitif que cela soit, cette série peut être due au hasard.)

Le tricheur moyen est loin de posséder la maîtrise mathématique et le goût du travail parfait qui animent l'un des personnages de Cryptonomicon, lorsqu'il calcule la quantité de faux indices qu'il doit semer pour que les statisticiens de l'armée allemande considèrent le pourcentage de pertes mystérieuses de leurs navires comme étant « statistiquement vraisemblable », au lieu de conclure que leur code a été cassé.

(Maintenant que Stack Overflow a publié ses données, il serait intéressant de voir ce que Levitt peut déduire d'une telle masse d'informations.)

Levitt professe un culte des données : il se présente comme esprit libre, qui déduit des affirmations à partir des données, sans idées préconçues. Ainsi, il montre facilement qu'une piscine est bien plus dangereuse qu'une arme à feu lorsqu'on a des enfants en bas âge à la maison (Bruce Schneier a déjà longuement écrit sur le thème de notre difficulté à apprécier correctement l'ampleur relative des risques.) Mais, évidemment, dans le contexte des États-Unis, rien de ce qui concerne les armes à feu ne peut être vu commme neutre...

Levitt peut d'autant moins se réclamer d'une approche neutre qu'il répète à plusieurs reprises des énormités non prouvées, comme celle d'une hérédité du QI. En dépit de ses propres principes, il ne cite cette fois aucune référence (et pour cause, la plus connue était une fraude). Il reprend même la légende des N % de l'intelligence qui serait due à l'hérédité, montrant ainsi que son ignorance proclamée des maths n'est pas de la fausse modestie : il n'a effectivement pas compris que toutes les grandeurs ne sont pas additives.

Une autre polémique avait fait rage autour de Freakonomics, celle autour de l'avortement. Levitt montre que la criminalité baisse juste au moment où la légalisation de l'IVG a produit ses effets, lorsque les enfants non désirés qui seraient nés sans cette légalisation arrivent à l'âge où on commence une carrière criminelle. Mais simplement poser comme principe le droit des femmes à disposer de leur corps n'est pas acceptable pour un économiste digne de ce nom et Levitt se sent donc obligé de passer du temps à des calculs compliqués et sans base objective sur la « valeur » comparée d'un fœtus et d'un nouveau-né.

Beaucoup plus ethnologique est son étude des prénoms comparés des noirs et des blancs aux États-Unis. J'y ai appris que la ségrégation reste toujours très forte. Noirs et blancs vivent dans le même pays, mais pas dans la même nation. Leurs goûts en matière de feuilletons télévisés débiles ne sont pas les mêmes (et la différence est statistiquement très nette), de même que leurs choix des prénoms. Aujourd'hui, les blancs nomment leurs garçons Jake, Connor, Tanner, Wyatt ou Cody, les noirs DeShawn, DeAndre, Marquis, Darnell ou Terrell. Est-ce que ce prénom va influencer l'avenir de l'enfant ? Si oui, cette influence est difficile à démêler au milieu de tous les autres et Levitt, qui croit fermement à la réussite individuelle aurait sans doute noté, si on livre avait été publié plus tard, que s'appeler Barack plutôt que John n'était pas forcément un handicap...

Pour synthétiser, Levitt est-il de gauche ? Globalement non, notamment en raison de sa croyance aveugle dans les beautés du marché. Mais la comparaison des gangs de vendeurs de drogue avec McDonald's est très pertinente (dans les deux cas, l'entreprise est florissante et gagne beaucoup d'argent mais l'employé de base est payé des clopinettes et fait un travail très pénible, le tout s'appuyant sur une des rares études des finances d'un gang).

L'auteur et quelques personnes qui se réclament des mêmes méthodes, s'exprime aujourd'hui sur le blog Freakonomics.


L'article seul

DNSCurve, la sécurité pour le DNS ?

Première rédaction de cet article le 20 Juin 2009


Tout le monde sait que le DNS n'est pas sûr. Si quelqu'un n'est pas au courant, il suffit de l'envoyer lire le RFC 3833. En 2008, la faille Kaminsky avait encore remis le dossier de la sécurité DNS sur le dessus de la pile. Les risques étant de natures très variées, il n'y a certainement pas de solution miracle à tous les problèmes de sécurité du DNS. Mais comme l'analyse sérieuse des problèmes de sécurité est difficile, comme la « balle d'argent » est une histoire plus vendeuse que la longue liste des mesures à prendre, on voit souvent une technologie particulière présentée comme la solution à tout. Vu l'expérience avec d'autres créations de Daniel Bernstein, il est probable que DNSCurve sera ainsi promu.

Avant de regarder DNSCurve et ses différences avec le protocole DNSSEC, revenons en arrière sur la sécurité du DNS. Lorsqu'un serveur DNS faisant autorité pour une zone envoie une réponse à un résolveur, un serveur DNS récursif, un méchant peut répondre avant le bon serveur et voir sa réponse acceptée par le résolveur, qui sera alors empoisonné (le RFC 5452 contient une explication plus détaillée et des calculs de la probabilité de réussite). C'est l'une des principales vulnérabilités du DNS.

Pour résoudre ce problème, il y a deux approches. Pour reprendre le vocabulaire du RFC 3552, on peut choisir de sécuriser le canal (vérifier qu'on parle bien à la machine à qui on voulait parler) ou de sécuriser le message (vérifier que le message vient bien de l'autorité et qu'il n'a pas été modifié en route). Les deux méthodes ne sont d'ailleurs pas contradictoires, on peut aussi choisir une défense en profondeur en adoptant les deux approches. En général, la sécurisation du message offre le maximum de sécurité (même si des intermédiaires ne sont pas dignes de confiance, on peut s'assurer de l'authenticité du message, ce qui est une propriété essentielle pour le DNS, qui dépend de tels intermédiaires, les serveurs cache) mais la sécurisation du canal est souvent plus facile à déployer.

Le débat existe pour tous les protocoles Internet. Par exemple, pour le courrier électronique, PGP sécurise le message, SMTP sur TLS (RFC 3207) sécurise le canal.

Dans le monde du DNS, la première solution, sécuriser le canal, est possible grâce à de nomnbreuses technologies :

  • TSIG (RFC 2845), une signature de la transaction grâce à une clé secrète partagée entre les deux serveurs,
  • SIG(0) (RFC 2931), une signature de la transaction utilisant des clés publiques,
  • Diverses techniques de résistance à la fraude, comme le choix au hasard du port source (RFC 5452),
  • Puisqu'une grande partie de la vulnérabilité du DNS vient de la taille trop petite du Query ID, il y a des propositions de l'étendre comme les DNS cookies (Internet-Draft draft-eastlake-dnsext-cookies) ou EDNS ping (Internet-Draft draft-hubert-ulevitch-edns-ping),
  • IPsec (RFC 3401), si seulement il était déployé,
  • Un protocole de transport résistant naturellement aux tricheries, comme TCP ou SCTP,
  • Et pourquoi pas des datagrammes protégés par TLS comme avec DTLS (RFC 4347),
  • Et enfin DNSCurve.

Toutes ces techniques ont leurs avantages et leurs inconvénients, certaines (comme celles du RFC 5452) ont eu de grands succès (comme de rendre peu exploitable la faille Kaminsky) mais toutes ont en commun de ne sécuriser que le canal entre deux serveurs. Si un serveur esclave d'une zone est contrôlé par un méchant et modifie les données, s'assurer qu'on parle bien à ce serveur ne servira à rien. Idem dans le cas, beaucoup plus fréquent, où un résolveur/cache d'un FAI ment à ses propres clients (cf. RFC 4924, section 2.5.2).

Pour la sécurisation du message, il n'existe à l'heure actuelle qu'une seule méthode, DNSSEC (RFC 4033 et suivants).

Revenons donc à DNSCurve. Cette technique n'est pas normalisée à l'heure actuelle et ne fait pas l'objet d'une spécification détaillée. Il existe juste une présentation sommaire sur le site officiel. Le principe de DNSCurve est que le dialogue entre deux serveurs est chiffré (par un algorithme de la famille des courbes elliptiques, Curve25519) et authentifié. La clé publique du serveur auquel on parle est obtenue dans le DNS, c'est le nom du serveur. Si le TLD .example est délégué à uz5xgm1kx1zj8xsh51zp315k0rw7dcsgyxqh2sl7g8tjg25ltcvhyw.nic.example, le résolveur peut, en examinant ce nom, voir que la délégation est sécurisée par DNSCurve, trouver la clé publique, et interroger le serveur de manière sûre, grâce au chiffrement asymétrique. Seule la communication entre deux serveurs est protégée. Si un des serveurs est une machine pirate, DNSCurve ne sert à rien.

Donc, DNSCurve n'est pas réellement un concurrent de DNSSEC, il traite un problème assez différent. Sur le site officiel, on trouve une comparaison de DNSCurve avec DNSSEC qui est intellectuellemnt très malhonnête, comme presque toujours avec Daniel Bernstein. Notamment, il manque dans le tableau deux points :

Type of security          DNSSEC                   DNSCURVE

Integrity despite         Protects against         Does not protect against
rogue secondary name      it                       it
servers of resolvers

Ability to follow         The actual algo-         Only one algorithm,
the progress in           rithm is not hardwired   if it is broken,
cryptography              in the protocol. New     everything is over.
                          algos can be added.

J'ai signalé ces deux points au mainteneur mais évidemment sans résultat. Le premier de ces deux points vient directement du fait que DNSCurve protège le canal et pas le message. Le second vient du fait que l'algorithme utilisé a été très peu analysé pour l'instant.

Il y a bien d'autres énormités. Par exemple, dire que « NS cache software (e.g., dnscache or PowerDNS Recursor or Unbound or BIND) - Administrator upgrades to a cache that supports DNSSEC. - Administrator upgrades to a cache that supports DNSCurve. » alors que Unbound et BIND ont le support DNSSEC depuis de nombreuses années et que presque tous les administrateurs DNS l'ont déjà installé, même s'ils ne l'ont pas activé (c'est un point important lorsqu'on veut déployer DNSSEC et qu'on se demande si ses serveurs secondaires habituels le géreront). Ou affirmer que NSEC3 est « almost always breakable » en donnant zéro explication (il n'existe actuellement aucune attaque publiée contre NSEC3). Cela se nomme du FUD. Encore un autre exemple de propagande très lourde, dire que « [Avec DNSSEC, ] Administrator must generate new signatures every month » alors qu'une telle contrainte n'apparait nulle part dans le protocole DNSSEC (c'est le signeur qui choisit la durée de validité des signatures, un mois est juste la valeur par défaut du signeur inclus dans BIND). Et, avec DNSCurve, c'est à chaque requête qu'il faut générer une nouvelle signature.

Que peut-on dire encore en comparant ces deux protocoles ? D'abord que DNSSEC a été soigneusement conçu pour que le serveur n'aie aucune opération de cryptographie à faire « par requête » (à l'exception de NSEC3 et encore il ne s'agit que de hachage, pas de chiffrement asymétrique). Toutes les opérations de signature peuvent être faites hors-ligne, une fois pour toute, sur la zone. Au contraire, DNSCurve exige des opérations de signature à chaque requête. Il ne convient donc pas à de gros serveurs.

Il est amusant de noter que la page officielle de DNSCurve reproche à DNSSEC que « DNSSEC reduces existing confidentiality by publishing the complete list of "secured" DNS records. This publication is integrated into the DNSSEC protocol; » alors que cette publication est inhérente au fait de signer la zone et pas chaque requête. Si on est prêt à faire des opérations de cryptographie à chaque requête, les solutions des RFC 4470 et RFC 5155 résolvent le problème de la confidentialité des données.

On peut terminer par un point amusant : avec DNSCurve, la clé publique est encodée dans le nom de la machine. C'est astucieux. Mais les clés de la cryptographie asymétrique sont très longues et les composants d'un nom de domaine sont limités à 63 octets. Cela a deux conséquences :

  • Pour les zones qui limitent la taille d'une délégation à 512 octets, comme la racine du DNS, on ne peut utiliser qu'un maximum de trois serveurs avec DNSCurve (car il faut aussi laisser de la place pour la question, et pour la colle).
  • DNSCurve n'utilise donc pas les courbes elliptiques parce qu'elles sont meilleures que les algorithmes basés sur la factorisation des nombres premiers (comme RSA) mais simplement parce qu'elles permettent des clés plus courtes. Le fait de les mettre dans les noms de domaine va sérieusement contraindre l'évolution ultérieure de DNSCurve. Il sera par exemple impossible d'augmenter la taille des clés.

Merci à Kim Minh Kaplan pour ses nombreuses et pertinentes remarques sur DNSCurve.


L'article seul

RFC 5533: Shim6: Level 3 Multihoming Shim Protocol for IPv6

Date de publication du RFC : Juin 2009
Auteur(s) du RFC : E. Nordmark ((Sun), M. Bagnulo (UC3M)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF shim6
Première rédaction de cet article le 18 Juin 2009


Quelles sont les méthodes disponibles dans l'Internet d'aujourd'hui pour assurer une plus grande fiabilité à un site ? On peut payer plus cher son FAI mais cela ne garantit pas une meilleure fiabilité. Aujourd'hui, la seule méthode réaliste est d'avoir plusieurs FAI, le multihoming. Mais cela implique d'avoir ses adresses IP à soi et de faire du BGP avec ses FAI. Si on utilise en effet des adresses « appartenant » à un des FAI, la panne de celui-ci empêchera de les utiliser, même si l'autre connexion est intacte. Comme les protocoles de transport comme TCP sont liés aux adresses IP utilisées, cela veut dire que toutes les sessions TCP existantes seront cassées. Ce n'est pas forcément génant pour HTTP, aux connexions souvent courtes, mais bien plus grave pour, par exemple, SSH. Le multihoming sans BGP permet donc d'établir de nouvelles connexions, mais pas de faire vivre les anciennes.

Il faut donc faire du BGP. Mais c'est complexe et, surtout, cela impose de mettre ses adresses IP dans la table de routage globale, qui est déjà bien chargée. Shim6, objet de ce RFC, propose une autre solution, lier les connexions à un groupe d'adresses IP, pas à une seule comme actuellement. On pourrait alors changer l'adresse utilisée en couche 3 sans dommage pour la couche 4. Shim6 dispense donc d'utiliser BGP.

Ce nouveau protocole dépend d'IPv6, comme son nom l'indique, car sa sécurité et même son fonctionnement de base nécessitent des mécanismes propres à IPv6. Shim6 se situe conceptuellement entre la couche 3 et la couche 4, d'où son nom de shim (mince couche entre deux vraies couches). On peut noter que SCTP (RFC 3286) part d'une idée proche (le groupe d'adresses IP) mais fonctionne, lui, dans la couche 4. Shim6, au contraire, est accessible à tous les protocoles de transport comme, par exemple, le TCP traditionnel.

Une autre façon de s'attaquer au même problème est de séparer complètement l'identificateur du localisateur comme le fait HIP, ce qui permet de traiter des problèmes supplémentaires comme la mobilité. Cette séparation est explicitement marquée comme « non-but » dans Shim6, qui ne manipule que des « localisateurs » même si l'un d'eux, baptisé « identificateur » sans l'être vraiment, a un rôle particulier dans l'association (section 1.3, qui définit les ULID - Upper-Layer Identifier).

Il existe donc désormais un grand choix de protocoles, tous prometteurs mais tous aussi marginaux les uns que les autres.

La gestation de ce protocole a été longue et difficile. Au final, Shim6 est un protocole très complexe, avec un RFC de plus de 130 pages (sans la détection de panne, qui fait l'objet d'un document séparé, le RFC 5534).

La section 1 expose les principes de base de Shim6. Le protocole est mis en œuvre complètement dans les machines finales, il ne nécessite aucune participation des routeurs. Chaque machine a un jeu d'adresses IP possibles, a priori toutes liées à un FAI (Shim6, contrairement à BGP, n'impose pas d'utiliser des adresses PI - Provider Independant). À un moment donné, une paire d'adresses (source et destination) est utilisée pour la communication effective et les machines peuvent changer de paire par la suite (le mécanisme de détection des pannes, ou des autres raisons qui qui déclencheraient un changement de paire, sera spécifié dans un autre document). Pour assurer qu'une machine ne puisse pas prétendre être utilisatrice d'une adresse IP quelconque, des adresses HBA (Hash Based Addresses) ou CGA (signées par la cryptographie) sont utilisées.

La section 1.1 détaille le cahier des charges et la 1.2 l'anti-cahier des charges, les questions qui étaient hors-sujet pour Shim6. Dans le cahier des charges, cette section 1 insiste notamment sur le côté « opportuniste » de Shim6 : contrairement à HIP, une association Shim6 peut commencer sans échange particulier et être établie bien après la connexion TCP. D'autre part, outre la résistance aux pannes, Shim6 doit permettre la répartition de charge, selon des paires d'adresses IP différentes. Dans l'anti-cahier des charges, se trouve la question de la rénumérotation, suite à des changements de FAI. Shim6 ne la traite pas (section 1.5) : l'ensemble des adresses IP disponibles doit être fixe, ou, en tout cas, changer lentement. Shim6 ne permet donc pas le nomadisme, même si celui-ci pourra être traité dans le futur. En attendant, au début de l'association Shim6, toutes les adresses IP possibles doivent être connues.

Shim6 est un protocole de couche « 3,5 », entre la couche de réseau et la couche de transport (section 1.6). Les protocoles situés au dessus de Shim6, comme TCP ou UDP utilisent des adresses IP habituelles, les ULID (Upper-Layer Identifier, voir la section 2.1 pour une terminologie complète). En dessous d'eux, Shim6 va fabriquer des paquets IP dont les adresses sources ou destination pourront être des ULID (puisque Shim6 ne sépare pas localisateurs et identificateurs) ou bien des purs localisateurs, différents des ULID que continue à utiliser le protocole de transport. Shim6 maintient, pour chaque paire d'ULID, la liste des localisateurs possibles (les adresses IP utilisables pour la communication).

Après une section 2 de terminologie et de notations, puis une section 3 sur les pré-supposés de Shim6, voici venir le protocole lui-même en section 4. L'essentiel du fonctionnement de Shim6 peut être résumé dans les étapes suivantes :

  • Une machine A en contacte une autre, B, par les moyens classiques, sans Shim6. Par exemple, elle établit une connexion TCP.
  • Une des deux machines, ou les deux, décide que ce serait une bonne idée de faire du Shim6. Par exemple, plus de N paquets ont été échangés et il semble donc bien que la connexion va durer, ce qui rend intéressant de la faire résister aux pannes. Un contexte Shim6 (une association entre les deux machines) est alors mis en place.
  • Les échanges continuent comme avant. À ce stade, bien que le contexte Shim6 aie été configuré, rien n'apparait dans les paquets échangés.
  • Une panne survient, rendant impossible d'utiliser les localisateurs actuels (les adresses IP de A et B). C'est là que Shim6 sert à quelque chose, une nouvelle paire de localisateurs est choisie et un en-tête d'extension est ajouté désormais à chaque paquet, pour indiquer le contexte utilisé (section 4.1). Ce contexte servira à trouver les ULID (les identificateurs) utilisés. Shim6 réécrira alors les paquets avant de les transmettre à TCP... qui ne se sera rendu compte de rien, il utilisera les mêmes adresses IP tout le temps (section 12 pour les détails sur la réception des paquets).

Donc, on ne peut pas, en observant le réseau, savoir si un contexte Shim6 est disponible car les paquets ne sont modifiés (par l'ajout de l'en-tête d'extension Shim6) que si la première paire de localisateurs devient inutilisable.

Ces en-têtes d'extension sont une nouveauté d'IPv6 (RFC 2460, section 4). Leur présence dans les paquets IPv6 est rare aujourd'hui mais des techniques comme Shim6 pourrait la rendre plus fréquente (si les différents pare-feux acceptent de les laisser passer). La section 4.6 décrit le placement de l'en-tête Shim6.

Les informations mémorisées par chaque machine (ULID de la connexion, localisateurs possibles, contextes, etc) sont décrites dans la section 6.

Comme toute technique d'indirection, c'est-à-dire comme toutes les techniques qui découplent, si peu que ce soit, l'identificateur et le localisateur, Shim6 est vulnérable aux attaques portant sur la correspondance entre utilisateur et localisateur. Qu'est-ce qui empêche un méchant, par exemple, d'ajouter une adresse IP qu'il ne contrôle pas légitimement, au jeu des localisateurs, pour que son partenaire Shim6 y envoie des paquets ? La section 4.4 résume les techniques utilisées pour empêcher cela, qui tournent autour d'adresses cryptographiques (RFC 3972 et RFC 5535) et de tests de la connectivité effective (voir si le pair répond avant de le noyer sous les paquets, cf. section 5.13 pour le message Probe).

Comment se fait l'établissement du contexte Shim6 ? Par un échange de quatre paquets (comme dans HIP) et non pas de trois paquets comme avec TCP. La section 4.5 résume ces quatre paquets, I1, R1, I2 et R2, pour lesquels l'explication complète est en section 7 (et la machine à états dans la section 20). La liste des localisateurs connus est envoyée dans ces paquets mais elle peut être modifiée par la suite, Shim6 ayant d'autres messages de contrôle que ces quatre-ci (section 10).

Le format des messages sur le câble est normalisé dans la section 5. Les messages de contrôle de Shim6 ne circulent ni sur UDP, ni sur TCP mais dans son propre protocole, de numéro 140 (section 17). Le début du paquet est le même pour tous les messages Shim6 (section 5.1). Les messages de la communication en cours ont le 16ème bit à 1 et sont décrits dans la section 5.2. Les messages de contrôle ont ce 16ème bit à zéro et font l'objet de la section 5.3.

Comment se fait le premier contact ? La procédure est synthétisée dans la section 13. La méthode recommandée est d'essayer toutes les adresses disponibles, de ne pas renoncer simplement parce qu'une paire d'adresses émetteur-récepteur ne fonctionne pas. Le problème de cette méthode est que, mise en œuvre directement (boucler sur toutes les adresses de la liste de struct addrinfo renvoyée par getaddrinfo() et tenter un connect() sur chacune) va mener à de longs délais puisqu'il faudra attendre l'expiration du délai de garde. Idéalement, il faudrait donc tenter des connexions non-bloquantes simultanément.

Comment est établi un contexte Shim6 ? La section 7 fournit toutes les informations nécessaires. L'échange nécessite quatre paquets (section 7.3), ce qui permet (contrairement à l'échange à trois paquets de TCP) de ne pas garder d'état dès le premier paquet, tout en permettant l'usage d'options. Cette section couvre également la vérification des adresses utilisées (section 7.2), vérification, par la cryptographie, que le « propriétaire » d'un ULID a le « droit » d'utiliser ces localisateurs et vérification que ces localisateurs fonctionnent.

Un problème classique de toute séparation, même partielle, entre l'identificateur et le localisateur, est le traitement des erreurs envoyées par ICMP. L'émetteur de ces erreurs ne connait pas forcément Shim6 et gère simplement des adresses IP. Il faut donc, sur réception d'un message ICMP, retrouver le contexte Shim6 (section 8) et transmettre le message ICMP à la couche de transport puisque, dans la plupart des cas, c'est elle qui doit être informée des erreurs. Cela nécessite de réécrire le message pour remplacer les adresses IP par des ULID, les seules adresses que connaisse la couche 4.

Arrivé à ce point du RFC, il est temps de voir quelques contraintes d'implémentation. Elles sont regroupées dans la section 15. D'abord, si Shim6 change la paire de localisateurs utilisée, les paquets IP passeront probablement par un chemin bien différent, et Shim6 devrait donc (section 15.1) prévenir la couche de transport que la congestion sera probablement différente.

Tout nouveau protocole qu'on tente de déployer sur l'Internet doit se battre contre les middleboxes, les équipements intermédiaires qui refusent fréquemment les paquets inconnus. La section 15.2 soulève le problème. Par exemple, quoique que Shim6 soit une technique entièrement « machine », qui est gérée uniquement aux extrêmités du réseau (section 15.3), il est toujours possible qu'un pare-feu bloque les paquets Shim6. Cela apparaitra à chacune des machines comme une absence de Shim6 chez le pair. Outre cette paranoïa générale des pare-feux, Shim6 soulève des questions particulières puisque l'autorisation ou l'interdiction des paquets devraient, idéalement, prendre en compte les ULID, pas les adresses IP du paquet. Shim6 aura donc les mêmes difficultés que HIP ou SCTP à s'installer dans un Internet très ossifié.

Enfin, certaines applications passent des adresses IP à leurs pairs (par exemple les téléphones SIP) et elles devraient donc s'assurer (section 15.4) qu'elles n'envoient pas uniquement l'ULID mais un nom de domaine ou bien le jeu complet de localisateurs.

Et la sécurité ? La section 16 résume les problèmes spécifiques à Shim6 (cf. RFC 4218) et les solutions possibles. Contre le risque d'usurpation d'adresses IP, les adresses cryptographiques, HBA (RFC 5535) et CGA (RFC 3972). Contre les attaques par inondation (en redirigeant du trafic vers une machine innocente), les tests d'atteignabilité (vérifier que la machine répond positivement avant de lui transmettre des gigaoctets).

En parlant d'implémentations, quelle est la situation de Shim6 de ce côté ? Une équipe coréenne avait mis en œuvre Shim6 pour le noyau Linux. Leur travail avait été décrit dans l'Internet-Draft draft-park-shim6-implementation mais le travail semble ne pas être allé plus loin. Plus récemment, une autre équipe, belge, avait réalisé LinShim6, sur le même système et documenté dans l'Internet-Draft draft-barre-shim6-impl. Il existe aussi une autre implémentation Linux dans l'OpenHIP. Une liste à jour des tentatives se trouve sur le site « officiel ».

Terminons ce résumé du RFC 5533 avec les annexes. La section 19 décrit de possibles extensions futures du protocole. Celui-ci est déjà bien assez compliqué comme cela et beaucoup d'idées ont été laissées à de futures nouvelles versions, comme la pré-vérification d'une paire de localisateurs de secours, l'interaction avec Mobile IP ou encore la normalisation d'une API. À l'heure actuelle, il n'existe pas d'API standard pour l'application qui voudrait être consciente de Shim6 et, par exemple, forcer ou, au contraire, interdire son utilisation (section 4.3).

L'excellente section 22 intéressera les étudiants en réseaux informatiques et tous les curieux qui se demandent « mais pourquoi les choses sont-elles comme elles sont ? ». Elle est en effet consacrée aux choix alternatifs, à ce qui aurait pu être. Parmi les idées envisagées et finalement abandonnées, on trouve d'autres solutions de sécurité (section 22.4) comme l'utilisation de cookies lors de l'établissement de l'association. La connaissance du cookie prouvait qu'on était toujours le même pair. Mais le cookie aurait été vulnérable lors d'une écoute du réseau. D'où le choix final des adresses cryptographiques. Dans le cas de HBA (RFC 5535), tous les localisateurs (toutes les adresses IP) sont liées et on peut donc vérifier que deux adresses appartiennent au même ensemble HBA. Par contre, HBA ne permet pas d'ajouter ou de supprimer des localisateurs. Dans le cas de CGA (RFC 3972), l'ULID est une clé publique (plus rigoureusement, un résumé d'une clé publique) et signe les localisateurs, ce qui autorisde l'ajout de localisateurs. Mais CGA dépend donc de la cryptographie à clé publique, plus coûteuse.

La création d'un contexte Shim6 (ce que j'ai baptisé association) se fait avec quatre paquets. On aurait pu en utiliser moins (section 2.5), pour accélerer l'association mais le choix a finalement été de privilégier la protection contre les attaques DoS. Une des raisons du choix est que, de toute façon, l'établissement de l'association ne bloque pas la connexion qui utilise Shim6, puisque les premiers paquets peuvent passer avant que Shim6 ne crée son contexte. Ce n'est donc pas trop grave si l'association prend du temps.


Téléchargez le RFC 5533


L'article seul

RFC 5535: Hash Based Addresses (HBA)

Date de publication du RFC : Juin 2009
Auteur(s) du RFC : M. Bagnulo (UC3M)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF shim6
Première rédaction de cet article le 18 Juin 2009


Les HBA (Hash Based Addresse) de ce RFC sont des adresses IPv6 fondées sur un résumé cryptographique d'un certain nombre de valeurs, permettant de garantir que toutes les adresses HBA d'un groupe donné sont liées. Elles sont notamment utilisées par le protocole SHIM6 (RFC 5533). Contrairement aux adresses CGA du RFC 3972, dont elles sont proches, les HBA ne nécessitent pas de cryptographie asymétrique. Par contre, elles ne permettent pas de prouver le lien entre une machine et une adresse IP, juste de prouver que les adresses d'un groupe ont été générées par la même machine.

Quel problème essaient de résoudre les HBA (section 1 du RFC) ? Dans les protocoles conçus pour le multihoming comme SHIM6 (RFC 5533), une machine annonce à ses pairs qu'elle a plusieurs adresses IP. Qu'est-ce qui l'empêche d'annoncer des adresses qui ne sont pas « à elle », soit pour capter le trafic d'un tiers, soit pour réaliser une DoS contre ce tiers en provoquant l'envoi de nombreux paquets vers lui ? SHIM6 utilise deux mécanismes proches, les CGA et les HBA. Dans les deux cas, les adresses IP annoncées peuvent être vérifiées cryptographiquement. CGA signe les adresses avec une clé asymétrique donc le pair peut vérifier qu'une adresse « appartient » bien au pair. HBA procède différemment : une adresse HBA est composée de deux parties, un préfixe de 64 bits et un identificateur local (également de 64 bits) qui est un résumé de l'ensemble des préfixes IP de la machine (puisque HBA est fait pour les machines multihomées, il y a plus qu'un préfixe) et d'un nombre aléatoire. HBA ne permet donc pas de prouver l'appartenance d'une adresse IP à une machine, uniquement le fait que toutes les adresses HBA d'un même groupe sont liées. Une machine SHIM6 qui utilise HBA ne pourra donc pas ajouter ou retirer des adresses IP au jeu d'adresses annoncé (elle le pourrait avec CGA) et HBA ne convient donc pas à des problèmes comme la renumérotation d'un réseau ou comme la mobilité. Mais le pair pourra vérifier, lors de l'établissement de l'association SHIM6, que ces adresses sont toutes liées à la même machine et sans les calculs longs et compliqués de la cryptographie asymétrique. HBA est donc plus économe. (Voir la section 3.3 pour une discussion plus détaillée du cahier des charges de HBA.)

Autrement, HBA et CGA sont très proches, utilisent les mêmes formats et ont également en commun de ne pas nécessiter de PKI.

Les risques de sécurité que les HBA traitent sont résumés en section 3.1, suivant le RFC 4218 (et traités en détail dans la section 11). En gros, les deux attaques possibles sont le détournement (un méchant va essayer d'obtenir des paquets qui n'étaient pas normalement pour lui) et l'inondation (un méchant va essayer de noyer une victime sous un flot de paquets). HBA protège uniquement contre les premier risque, d'autres protocoles doivent gérer le second, par exemple en testant si le pair est joignable (ce que fait SHIM6).

La section 3.2 donne le principe de base de HBA : si une machine a trois préfixes IPv6, mettons A, B et C, elle choisit un nombre M au hasard puis génère trois adresses, une par préfixe. Pour A, elle concatène au préfixe le résumé cryptographique de la concaténation de M, de A et la la liste des préfixes (A, B et C). Même principe pour les autres préfixes. Étant donné une liste de préfixes (rappelez-vous que, avec HBA, elle doit être stable), un préfixe et le nombre M, on peut donc vérifier qu'une adresse fait bien partie du groupe, en recalculant le résumé.

Une particularité des HBA est qu'elles sont normalisées comme étant une variante des CGA du RFC 3972. La section 4 de notre RFC est donc dédiée à l'étude de la compatibilité exacte entre HBA et CGA. Cette compatibilité permet d'avoir des adresses qui sont à la fois HBA et CGA. On peut donc les utiliser dans des protocoles comme SEND (RFC 3971), qui impose des CGA. Il y a donc trois types d'adresses :

  • Les CGA pures, normalisées dans le RFC 3972, qui n'incluent donc pas la nouvelle extension « multi-préfixe » (section 5),
  • Les HBA pures qui n'utilisent pas du tout de clé publique,
  • Les mixtes qui sont liées à la fois à une clé publique (CGA) et à un jeu de préfixes.

Quelle est cette extension « multi-préfixe » ? Décrite dans la section 5 de notre RFC, elle permet de représenter l'ensemble des préfixes liés par HBA. C'est cet ensemble qui servira d'entrée au hachage cryptographique.

Le mécanisme exact de génération est en section 6. Il est dérivé de celui de CGA, en section 4 du RFC 3972. La plus grande différence avec CGA est que l'adresse ne peut pas être fabriquée sans connaître les préfixes. Toutes les adresses doivent donc être générées ensemble. L'entrée des fonctions cryptographiques comporte donc l'extension multi-préfixe, qui indique les préfixes, une clé publique (si l'adresse est une mixte CGA/HBA) ou une initialisation aléatoire (nonce) - encodée comme si c'était une clé publique CGA - si l'adresse est une HBA pure et les autres paramètres de CGA.

À ce stade, on a donc un ensemble d'adresses disponible pour la machine. Si une autre machine veut vérifier ces adresses, elle doit utiliser les techniques de la section 7. Par exemple, 7.2 expose comment vérifier qu'une adresse donnée fait bien partie d'un ensemble HBA. (On vérifie que le préfixe de l'adresse est dans l'ensemble indiqué dans l'extension multi-préfixe puis on refait le hachage et on vérifie que l'identificateur local, les 64 bits les plus à droite, sont bien les mêmes.)

La publication des adresses HBA dans le DNS fait l'objet de la section 9. La recommandation est que « ça dépend ». Selon que la machine a uniquement des adresses HBA ou pas, selon que les préfixes soient des ULA ou pas, il peut être raisonnable ou pas de publier ces adresses HBA dans les DNS. Puisqu'une adresse HBA ressemble à n'importe quelle autre adresse IPv6, le client qui les trouve dans le DNS ne sait pas s'il a affaire à une HBA ou pas et il doit donc déduire cela d'un autre mécanisme.

Une mise en œuvre de HBA en C se trouve en http://ops.ietf.org/multi6/francis-hba.tar.gz.


Téléchargez le RFC 5535


L'article seul

Le Péril bleu

Première rédaction de cet article le 17 Juin 2009


Parmi les écrivains français d'aventure de la « Belle Époque », Maurice Renard est nettement moins connu que Jules Verne ou J.-H. Rosny aîné. On cite souvent uniquement son livre « Les Mains d'Orlac ». Mais il est aussi l'auteur d'un fantastique roman, « Le Péril bleu ».

Le livre n'a pas été réédité depuis trente ans. Il s'inscrit clairement dans le courant du roman « pré-science-fiction » français. Je ne peux absolument pas raconter l'histoire, car beaucoup repose sur les surprises successives (et ne lisez donc pas l'article de Wikipédia avant le livre !). Dans une petite région tranquille de France, le Bugey, deux années avant la première guerre mondiale, dans un monde qui se croit stable, où les ouvriers restent à leur place, où les bourgeois républicains rêvent de marier leur fille avec un noble, où les allemands sont des méchants militaristes, où les policiers ont forcément d'épaisses moustaches et où les paysans sont assez abrutis, mais fidèles, de mystérieuses disparitions d'objets se produisent. Drôles au début, elles vont vite devenir plus dramatiques. Pour retrouver les mystérieux « Sarvants », comme les gens du pays appellent leurs persécuteurs, sur quoi peut-on compter ? Sur la science de l'astronome Le Tellier ou de son assistant Robert Collin ? Sur la fougue sportive du jeune, beau et riche duc d'Agnès ? Sur les capacités de déduction du détective amateur Tiburce ?

Le livre passe du tragique au très drôle (excellente caricature du concurrent des romains anglais, Sherlock Holmes, bien plus drôle que celle de Maurice Leblanc). Il y a d'innombrables pistes à suivre et toutes ne sont pas fausses...

À l'heure où on recommande les lectures de vacances, voici mon conseil : allez enquêter au Bugey. Et voyez si vous déduirez mieux que Tiburce.

Les érudits peuvent aussi emporter à la plage « La science-fiction française au XXe siècle (1900-1968) » de Jean-Marc Gouanvic (éditions Rodopi), qui détaille longuement le Péril bleu.


L'article seul

abuse@BIGISP.net ne répond pas

Première rédaction de cet article le 16 Juin 2009


Comment signale t-on un problème sur l'Internet ? Si un ver fait des requêtes HTTP répétées à la recherche d'une vulnérabilité ? Si un autre ver se connecte en SSH en permanence à votre Kimsufi en tentant le compte guest ? Si un script utilise automatiquement votre service en dépit des conditions d'utilisation ? Si vous recevez du spam depuis telle ou telle machine ? En théorie, il existe une adresse de courrier standard pour cela. Spécifiée dans le RFC 2142, c'est abuse@DOMAIN.EXAMPLEDOMAIN.EXAMPLE est le domaine en cause. Mais le courrier envoyé à abuse n'obtient jamais de réponse et, souvent, n'est même pas lu. Pourquoi ?

Au cours de ma carrière, j'ai été des deux côtés de la barrière, du côté de ceux qui reçoivent les messages envoyés à abuse et du côté de ceux qui les envoient. Du côté du simple utilisateur, qui voudrait signaler un problème, le bilan n'est pas très glorieux. Les réponses reçues sont rarissimes. La plupart du temps, c'est le silence complet. Parfois, on reçoit un avis de non-remise « il n'y a pas de compte abuse à l'adresse indiquée ». Parfois une réponse standard inutile.

Est-ce qu'au moins les messages sont lus, même si abuse ne peut pas répondre à tous ceux qui lui écrivent ? Même pas. La plupart du temps, les messages atterissent directement dans /dev/null, ou bien, à la rigueur, dans la boîte d'un stagiaire embauché pour faire le support utilisateur, qui n'y connait rien et n'est pas assez payé pour qu'on puisse lui demander de travailler sérieusement.

Même si certains gros FAI s'obstinent à nier et affirment, contre toute évidence, que les messages sont lus soigneusement, la triste réalité est qu'écrire à abuse ou bien jouer à World of Warcraft donneront le même résultat (et, dans le second cas, au moins, on voit les monstres qu'on combat).

Est-ce de la paresse ou de la méchanceté de la part des FAI ? En partie oui, bien sûr, c'est vrai qu'ils se moquent totalement de ce que leurs propres utilisateurs peuvent raconter. Alors, quand il s'agit d'inconnus... Mais il faut aussi remarquer que le problème n'est pas simple. D'abord, sur l'Internet, il y a tout le temps des problèmes. Si on héberge un parc de dix mille machines Unix dédiées et louées à des clients, il y en a forcément toujours un bon nombre qui se lancent dans des attaques, soit parce que le locataire est un méchant, soit parce qu'il a laissé un script PHP vulnérable sur sa machine. Si on connecte des dizaines de milliers de foyers où se trouvent des machines MS-Windows, c'est encore pire, un bon nombre a été transformé en zombies et les attaques qu'elles lancent vaudront certainement à abuse pas mal de courrier.

Mais il y a un autre problème : la très grande majorité des rapports envoyés à abuse sont inutilisables. Aucun détail, aucune précision, l'heure de l'attaque n'est pas indiquée (ou alors en heure locale, sans indication du fuseau horaire), les messages sont résumés et mal traduits au lieu d'être cités littéralement, etc. Le message typique reçu par abuse (rappelez-vous qu'un gros hébergeur peut avoir des dizaines de milliers de machines, voire davantage pour les très gros) est simplement « Mon firewall [sic] me dit que votre machine m'attaque, arrêtez tout de suite » (et encore, je n'ai pas respecté l'orthographe). Il est même fréquent que le message soit 100 % erroné et qu'il n'y aie pas d'attaque du tout (comme le voit fréquemment l'IANA).

Analyser de tels messages sérieusement nécessiterait du temps et une main d'œuvre qualifiée, alors qu'en général on met au « support de premier niveau » les gens les moins payés de l'entreprise. Il faudrait demander des précisions, faire preuve de patience, suivre le dossier, tout ce que fait un avocat d'affaires payé très cher de l'heure, mais pas ce que peut faire le marocain ou l'indien qui travaille dans un call center. Pour des simples raisons économiques, les messages à abuse sont donc traités vite et mal.

abuse est d'autant moins enclin à lire ses messages qu'un certain nombre d'utilisateurs spamment à tout vent et écrivent, lors de n'importe quel problème, à toutes les adresses de courrier qu'ils peuvent trouver en interrogeant whois.

Et les messages de qualité ? En général, vu l'afflux de messages inutilisables, les rares messages corrects, précis et complets sont traités comme les autres, perdus au milieu du flot.


L'article seul

RFC 4347: Datagram Transport Layer Security

Date de publication du RFC : Avril 2006
Auteur(s) du RFC : E. Rescorla (RTFM), N. Modadugu (Stanford University)
Chemin des normes
Première rédaction de cet article le 16 Juin 2009


Le protocole de cryptographie TLS, normalisé dans le RFC 5246, ne s'appliquait traditionnellement qu'à TCP. Les applications utilisant UDP, comme le fait souvent la téléphonie sur IP ne pouvaient pas utiliser TLS pour protéger leur trafic contre l'écoute ou la modification des données. Mais, désormais, cette limitation disparait, DTLS permet de protéger du trafic UDP.

TLS ne tournait que sur TCP car il avait besoin d'un transport fiable, garantissant que les données arrivent toutes, et dans l'ordre. Le grand succès de TLS (notamment utilisé pour HTTP et IMAP) vient de sa simplicité pour le programmeur : rendre une application capable de faire du TLS ne nécessite que très peu de code, exécuté juste avant l'envoi des données. Par contre, cela laissait les applications UDP comme SIP non protégées (section 1 de notre RFC). Les solutions existantes comme IPsec ne sont pas satisfaisantes, notamment parce qu'elles n'offrent pas la même facilité de déploiement que TLS, qui tourne typiquement dans l'application et pas dans le noyau.

DTLS a été conçu pour fournir TLS aux applications UDP. Il offre les mêmes services que TLS : garantie de l'intégrité des données et confidentialité.

La section 3 explique les principes de DTLS : protégé ou pas par DTLS, UDP a la même sémantique, celle d'un service de datagrammes non fiable. DTLS est une légère modification de TLS : il en garde les principales propriétés, bonnes ou mauvaises.

Mais pourquoi ne peut-on pas faire du TLS normal sur UDP ? Parce que TLS n'a pas été conçu pour tourner au dessus d'un protocole non fiable. TLS organise les données en enregistrements (records) et il ne permet pas de déchiffrer indépendamment les enregistrements. Si l'enregistrement N est perdu, le N+1 ne peut pas être déchiffré. De même, la procédure d'association initiale de TLS (handshake) ne prévoit pas de perte de messages et ne se termine pas si un message est perdu.

Le premier problème fait l'objet de la section 3.1. La dépendance des enregistrements TLS vis-à-vis de leurs prédécesseurs vient du chaînage cryptographique (chiffrement par blocs) et de fonctions anti-rejeu qui utilisent un numéro de séquence, qui est implicitement le rang de l'enregistrement. DTLS résoud le problème en indiquant explicitement le rang dans les enregistrements.

Et la question de l'association initiale est vue dans la section 3.2. Pour la perte de paquets lors de l'association, DTLS utilise un système de retransmission (section 3.2.1) et pour l'éventuelle réorganisation des paquets, DTLS introduit un numéro de séquence (section 3.2.2). En prime, DTLS doit gérer la taille importante des messages TLS (souvent plusieurs kilo-octets), qui peut être supérieure à la MTU. DTLS permet donc une fragmentation des paquets au niveau applicatif, un message pouvant être réparti dans plusieurs enregistrements (section 3.2.3).

Enfin, l'anti-rejeu a été modifié pour tenir compte du fait que la duplication de paquets, en UDP, n'est pas forcément malveillante (sections 3.3 et 4.1.2.5).

La définition formelle du nouveau protocole est en section 4. DTLS étant une légère évolution de TLS, la définition se fait uniquement en listant les différences avec TLS, dont il faut donc garder le RFC 5246 sous la main.

Au niveau du Record Protocol de TLS, l'enregistrement spécifié dans la section 6.2.1 du RFC 5246 gagne deux champs (section 4.1) :

  struct {
         ContentType type;
         ProtocolVersion version;
         uint16 epoch;                                    // NOUVEAU
         uint48 sequence_number;                          // NOUVEAU
         uint16 length;
         opaque fragment[DTLSPlaintext.length];
       } DTLSPlaintext;

notamment le numéro de séquence sequence_number (qui était implicite dans TLS, puisque TCP garantissait l'ordre des messages). Pour éviter la fragmentation et les ennuis associés, les mises en œuvre de DTLS doivent déterminer la MTU du chemin et n'envoyer que des enregistrements plus petits que cette MTU (section 4.1.1).

Contrairement à IPsec, DTLS n'a pas la notion d'identificateur d'association. Une machine qui reçoit du TLS doit trouver l'association toute seule, typiquement en utilisant le tuple (adresse IP, port).

En toute rigueur, DTLS n'est pas spécifique à UDP, il peut marcher sur n'importe quel protocole de transport ayant une sémantique « datagrammes ». Certains de ces protocoles, comme DCCP (cf. RFC 5238), ont leur propres numéros de séquence et ils font donc double emploi avec ceux de DTLS. Petite inefficacité pas trop grave.

Au niveau du Hanshake protocol, les modifications que DTLS apporte à TLS font l'objet de la section 4.2. Les trois principales sont :

  • L'ajout d'un gâteau pour limiter les risques de DoS,
  • Modification des en-têtes pour gérer les pertes ou réordonnancements des paquets (section 4.2.2),
  • Ajouts de minuteries pour détecter les pertes de paquets (section 4.2.4).

Les gâteaux de DTLS sont analogues à ceux de Photuris ou IKE (section 4.2.1). Le message ClientHello de la section 7.4.1.2 du RFC 5246 y gagne un champ :

 opaque cookie<0..32>;         // NOUVEAU

OpenSSL gère DTLS depuis la version 0.9.8 (on peut aussi consulter le site Web du développeur). Un exemple d'utilisation se trouve dans http://linux.softpedia.com/get/Security/DTLS-Client-Server-Example-19026.shtml. Il me reste à inclure ce protocole dans echoping. GnuTLS n'a pas de support DTLS (et aucun développement n'est en cours, les volontaires sont les bienvenus).

Une très bonne description de la conception de Datagram TLS et des choix qui ont été faits lors de sa mise au point, se trouve dans l'article The Design and Implementation of Datagram TLS, écrit par les auteurs du RFC. C'est une lecture très recommandée.


Téléchargez le RFC 4347


L'article seul

StackOverflow data to PostgreSQL

Première rédaction de cet article le 14 Juin 2009


The great social site Stack Overflow just announced the publication of its entire database under a Creative Commons free licence. I believe it is the first time such an important social networking site publishes its data, so it is a great day for data miners. In this small article, I will explain how I entered these data into a PostgreSQL database for easier mining. (The work was done on a Debian machine but it should work on any Unix.)

The original file is huge (200 megabytes today, and growing). To avoid killing the Stack Overflow servers, it is distributed in a peer-to-peer fashion with BitTorrent. I just downloaded the torrent file http://blog.stackoverflow.com/wp-content/uploads/so-export-2009-06.7z.torrent to my download directory and BitTorrent does the rest. I then extract the XML files with p7zip. Each XML file store a class of Stack Overflow objects:

  • Users, today 88,558 (but many of them registered once but never came back afterwards),
  • Posts, today 182,742 questions and 698,923 answers,
  • Comments, today 705,085,
  • Votes,
  • Badges.

I then created one SQL table for each class. The complete DDL instructions are in file so-create.sql. I can then create the database and its schema:

% createdb --encoding=UTF-8 so
% psql -f so-create.sql so

I am normally a fan of integrity constraints in databases. But, in the case of Stack Overflow, there are many obvious integrity constraints that I did not include because they are violated at least one, especially in old data (presumably during beta testing of the site). For instance, 18,239 users (20 %) has no name (see for instance http://stackoverflow.com/users/57428, the one with the highest reputation) and therefore I cannot write name TEXT NOT NULL.

Same problem with the accepted answer, some posts reference an answer which is not available (for instance post #202271 has an accepted answer in the XML file, #202526, which does not exist).

Once the database is set up, we just have to parse the XML files and to load them in the database. I choose the Python programming language and the ElementTree XML library. I produce SQL files which uses the COPY instruction.

The Python program is available as so-so2postgresql.py. To execute it, you just indicate the directory where the Stack Overflow XML files have been extracted. Then, you run PostgreSQL with the name of the produced SQL COPY file, and with the name of the database:

% python so-so2postgresql.py /where/the/files/are > so.sql
% psql -f so.sql so

This so-so2postgresql.py program requires a lot of memory, because it keeps the entire XML tree in memory (that is a weakness of the XML library used). Do not attempt to run it on a machine with less than eight gigabytes of physical RAM, swapping will make it awfully slow. You may also have to increase the available memory with ulimit.

Once the program is over, you can start studying the data:

so=>  SELECT avg(reputation)::INTEGER FROM Users;
 avg 
-----
 183
(1 row)

so=> SELECT avg(reputation)::INTEGER FROM Users WHERE reputation > 1;
 avg 
-----
 348
(1 row)

The first analysis produced with this database was an exploration of the "fastest gun in West" syndrome (available at stack-overflow-short-tail.html, in French).

Many people already posted on the subject of the Stack Overflow database. For instance:


L'article seul

Les participants à Stack Overflow travaillent-ils sur le long terme ?

Première rédaction de cet article le 14 Juin 2009


Le site social Stack Overflow ayant désormais rendu publique sa base de données, il est possible de l'analyser et d'étudier les comportements des participants à ce site. Par exemple, les votes et les réponses se poursuivent-ils pendant des mois ou bien sont-ils concentrés dans les heures qui suivent la publication d'une question ?

Regardons d'abord l'évolution des votes dans le temps. Combien de temps s'écoule t-il entre un message (question ou réponse) et les votes sur ce message ?


so=> SELECT DISTINCT (votes.creation - posts.creation::date) AS interval, 
so->                 to_char(count(votes.id)*100.0/
so(>                         (SELECT count(votes.id) FROM Posts, Votes 
so(>                                 WHERE Votes.post = Posts.id), '99.99') AS 
so->                  percent
so->    FROM Votes, Posts 
so->    WHERE Votes.post = Posts.id
so->    GROUP BY interval ORDER BY interval;         
 interval | percent 
----------+---------
        0 |  54.75
        1 |  10.33
        2 |   2.62
        3 |   1.74
        4 |   1.26
        5 |   1.01
        6 |    .87
        7 |    .70
        8 |    .57
        9 |    .49
       10 |    .44
       11 |    .42
       12 |    .38
       13 |    .39
       14 |    .39
       15 |    .33
       16 |    .30
...

La majorité absolue des votes ont lieu le jour de publication de l'article. Dès le cinquième jour, on passe en dessous d'un pour cent des votes pour la journée. Bref, il semble que le syndrôme connu à Stack Overflow sous le nom de fastest gun in West joue à plein. N'importe quel contributeur peut le voir : si on arrête d'écrire, la réputation ne bouge plus guère, les votes tardifs étant rares.

Cela se voit très bien sur le graphique, produit par gnuplot avec ces instructions :

set xrange [:150]
set format y "%.0f"
plot "votes-per-day.dat" using 1:2 with lines title ""

et en extrayant les données avec psql --file votes-per-day.sql --field-separator ' ' --tuples-only --no-align so > votes-per-day.dat, on obtient :

Mais la traîne est tellement longue qu'elle contribue quand même. Ainsi, 27 % des votes ont lieu plus d'une semaine après l'article et 19 % des votes plus d'un mois après :

so=> SELECT count(votes.id)*100/(SELECT count(votes.id) FROM Posts, Votes 
                                WHERE Votes.post = Posts.id) AS percent
       FROM votes, posts WHERE 
                   votes.post = posts.id AND 
                   votes.creation >= (posts.creation + interval '7 day')::date;
 percent 
---------
      27
(1 row)

so=> SELECT count(votes.id)*100/(SELECT count(votes.id) FROM Posts, Votes 
                                WHERE Votes.post = Posts.id) AS percent
       FROM votes, posts WHERE 
                   votes.post = posts.id AND 
                   votes.creation >= (posts.creation + interval '30 day')::date;
 percent 
---------
      19
(1 row)

Graphiquement, en mettant l'axe des Y en logarithmique, on voit mieux la longue traîne. Les instructions gnuplot sont :

set xrange [:150]
set logscale y
set format y "%.0f"
plot "votes-per-day.dat" using 1:2 with lines title ""

et le résultat est :

Et l'intervalle entre la question et une réponse acceptée (dans Stack Overflow, l'auteur d'une question peut marquer une question et une seule comme acceptée) ?

so=> SELECT DISTINCT(answers.creation::date - questions.creation::date) AS interval, 
so->        to_char((count(questions.id)*100.0/(SELECT count(answers.id) 
so(>                                  FROM Posts questions, Posts answers
so(>                                  WHERE answers.id = questions.accepted_answer AND 
so(>                                        questions.type = 1 AND
so(>                                        answers.type = 2)), '999.9') AS percent
so->     FROM Posts questions, Posts answers
so->     WHERE answers.id = questions.accepted_answer AND questions.type = 1 AND
so->           answers.type = 2
so->     GROUP BY interval ORDER by interval;
 interval | percent 
----------+---------
        0 |   83.9
        1 |    7.3
        2 |    1.5
        3 |    1.0
        4 |     .7
        5 |     .5
        6 |     .4
        7 |     .4
        8 |     .3
...

La courbe est encore plus brutale. On pourrait résumer en disant que, si on ne répond pas le premier jour, on n'a que peu de chances d'être accepté... Mais il faut garder espoir. 4 % des réponses acceptées ont été écrites plus d'une semaine après l'article et 1 % après un mois.

so=> SELECT count(answers.id)*100/(SELECT count(answers.id) 
                                FROM Posts questions, Posts answers
                                WHERE  answers.id = questions.accepted_answer AND 
                                       questions.type = 1 AND
                                       answers.type = 2) AS percent
       FROM Posts questions, Posts answers
       WHERE answers.id = questions.accepted_answer AND 
             questions.type = 1 AND answers.type = 2 AND
             answers.creation > (questions.creation + interval '7 day')::date;
 percent 
---------
       4
(1 row)

so=> SELECT count(answers.id)*100/(SELECT count(answers.id) 
                                FROM Posts questions, Posts answers
                                WHERE  answers.id = questions.accepted_answer AND 
                                       questions.type = 1 AND
                                       answers.type = 2) AS percent
       FROM Posts questions, Posts answers
       WHERE answers.id = questions.accepted_answer AND 
             questions.type = 1 AND answers.type = 2 AND
             answers.creation > (questions.creation + interval '30 day')::date;
 percent 
---------
       1
(1 row)

L'article seul

Internet est-il de gauche ?

Première rédaction de cet article le 10 Juin 2009


On pourrait se dire que oui. Après tout, ceux qui tapent le plus sur l'Internet, Sarkozy, Allègre, Val ou Lefebvre, sont tous des gens de droite (même si certains sont parfois, en raison d'une très ancienne adhésion à un parti de gauche, étiquetés différemment par les médias). Mais ces discours anti-Internet, méfiants, voire franchement hostile, face à la liberté que procure ce nouvel outil, se retrouvent bien au-delà de la droite. Et ceux qui ont construit Internet, étaient-ils de gauche ?

L'ancêtre d'Internet, l'Arpanet, a été développé, pendant des années, à 100 % sur fonds publics, les capitalistes n'aimant pas prendre des risques. Mais ces fonds n'étaient pas distribués pour la culture ou la recherche puisqu'ils venaient en totalité de l'armée états-unienne, via son agence de recherche, nommée à l'époque ARPA. Revoyons le contexte. C'était en pleine guerre du Viêt Nam. Sur les campus états-uniens en ébullition, il n'était pas toujours facile de trouver des chercheurs qui acceptaient de travailler pour l'armée. Aujourd'hui, la plupart des chercheurs ne prennent plus en compte des considérations politiques ou morales avant d'accepter un budget : l'argent n'a pas d'odeur. Mais, à l'époque, la prise de conscience était bien plus forte et tout un courant existait chez les scientifiques pour dire qu'on ne pouvait pas faire n'importe quelle recherche en éludant ses responsabilités. (Pour une bonne édition en français des textes de ce courant, voir le livre de Jean-Marc Lévy-Leblond et Alain Jaubert, « (Auto)critique de la science », publié au Seuil en 1973.)

Ceux qui ont créé Arpanet puis l'Internet étaient donc les moins hostiles à l'armée et à sa guerre en Asie du Sud-Est. Ce fut au point que d'autres réseaux, concurrents de l'Arpanet, refusaient tout simplement d'utiliser des techniques marquées comme « militaires ». Ce fut le cas de Fidonet, par exemple, très utilisé par les ONG, notamment en Afrique parmi les ONG d'aide au développement et qui prenait soin de développer ses propres protocoles.

Comme rien n'est jamais aussi simple, l'argument que l'Internet était un protocole de l'armée états-unienne avait aussi été utilisé de manière protectionniste (par exemple en France), pour travailler sur des protocoles spécifiques aux entreprises françaises, en rejetant le « protocole du DoD » comme TCP/IP était qualifié dans les livres de cours comme le Pujolle.

En fait, la complexité de la question vient d'un problème plus fondamental, la neutralité des objets techniques. Est-ce que le téléphone est de gauche ou de droite ? Est-ce que IPv6 est plus ou moins à gauche qu'IPv4 ? Ce genre de questions parait absurde et cela permet au chercheur de travailler sur n'importe quel sujet en disant qu'il est neutre, que ce sont les applications qui sont bonnes ou mauvaises mais certainement pas le sujet de recherche pour lequel il touche des subventions.

Or, s'il est vrai que le téléphone ou l'Internet peuvent être utilisés par tous, il est également vrai que de tels réseaux complexes et très coûteux à déployer ne naissent pas dans un garage : leur mise au point et, encore plus, leur sortie dans le monde réel nécessite que la société mette d'énormes moyens en jeu et elle ne le fera que s'il y a un intérêt (c'est la différence entre l'invention, phénomène individuel qui peut se faire dans un garage, et l'innovation qui est un phénomène social.). Un objet technique ne réussit pas sur ses propres mérites, mais parce que des gens riches et puissants y voient un intérêt.

Et, en même temps, il a toujours une ambiguité. Les objets techniques peuvent partiellement échapper à leur créateur, ils peuvent avoir des résultats inattendus et un réseau conçu pour l'armée de l'Empire peut servir à diffuser plein d'informations d'une manière qui était difficile et chère avant, peut servir à bâtir une encyclopédie libre et peut servir à bien d'autres choses qui auraient sans doute surpris les généraux de 1969...


L'article seul

Synchroniser deux dépôts darcs par courrier

Première rédaction de cet article le 9 Juin 2009


darcs est un excellent VCS décentralisé. Il est par exemple utilisé pour gérer les fichiers de ce blog. Permet-il de travailler sur plusieurs machines sans qu'aucun des dépôts ne soit connecté en permanence ? Oui, en échangeant des patches par courrier, patches qu'on applique ensuite localement.

D'abord, il faut demander à darcs de produire un patch. On peut utiliser pour cela la fonction shell darcsdiff, que présentait mon article sur les fonctions shell. darscdiff prend comme argument l'ID du dernier message du dépôt qui est en retard de synchronisation. (Ou, dit autrement, l'ID immédiatement précédent celui à partir duquel on veut envoyer les patches.) Si on prend un ID trop lointain dans le passé, ce n'est pas très grave (le message sera simplement trop gros), darcs n'appliquera que les patches nouveaux.

Donc, par exemple :

% darcsdiff 'Manque de scalabilite du Wifi' 
What is the target email address? stephane+darcs@bortzmeyer.org
Successfully sent patch bundle to: stephane+darcs@bortzmeyer.org.

(Attention, par défaut, darcsdiff envoie tout le dépôt s'il ne trouve pas l'ID donc lisez bien les avertissements avant de taper sur la touche Entrée.)

Et le destinataire reçoit un message du genre :

Subject: darcs patch: Article NetBSD format fini (and 26 more)
From: bortzmeyer@batilda.nic.fr
To: stephane+darcs@bortzmeyer.org
Date: Tue,  9 Jun 2009 09:04:00 +0200 (CEST)
X-Mail-Originator: Darcs Version Control System
X-Darcs-Version: 2.0.2 (release)
DarcsURL: CONTEXT

[-- Attachment #1 --]
[-- Type: text/plain, Encoding: quoted-printable, Size: 2.4K --]

Wed Jun  3 11:37:53 CEST 2009  stephane@ludwigVII.sources.org
  * Article NetBSD format fini

Wed Jun  3 13:25:43 CEST 2009  stephane@ludwigVII.sources.org
  * TODO: strlen et l'optimisation

...

Il doit alors sauvegarder le message (par défaut, l'attachement porte comme nom l'ID du premier patch qui était manquant) puis demander à darcs d'appliquer ces patches (il peut y en avoir plusieurs, comme dans l'exemple ci-dessus) :

% darcs apply article-netbsd-format-fini.dpatch 

Si on le fait deux fois, par erreur :

% darcs apply article-netbsd-format-fini.dpatch 
All these patches have already been applied.  Nothing to do.

On trouve de nombreux détails, notamment l'intégration avec mutt dans la documentation de darcs.


L'article seul

Format pour transmettre des données structurées sur le réseau

Première rédaction de cet article le 8 Juin 2009


La question du meilleur format pour transmettre des données structurées d'une machine du réseau à une autre a toujours suscité bien des discussions. Ce n'est peut-être pas aussi passionnel que le choix du langage de programmation mais cela fait toujours largement débat.

Le débat n'est pas toujours très informé. Sur les forums, on voit souvent, lorsque quelqu'un s'enquiert du « meilleur » format pour transmettre des données structurées, des réponses aussi idiotes que « XML est nul [sans autre explication], utilises JSON » ou bien « Il faut utiliser les Protocol Buffers [là aussi, sans explication, à part que c'est le protocole de Google et que Google a forcément raison] ». (Données « structurées » signifie qu'elles ne sont pas simplement des listes de tuples, donc un format comme CSV (RFC 4180) ne compte pas.)

Une discussion bien meilleure s'est déroulée récemment sur une liste de diffusion de l'IETF, apps-discuss, la liste du secteur Applications de l'IETF. On trouve son point de départ dans un article de Patrik Fältström. L'IETF n'a pas de format standard pour ses applications, SNMP (RFC 1157) utilise ASN.1, EPP (RFC 4930) et Netconf (RFC 4741) XML, etc.

Pour ceux qui n'ont pas participé aux N discussions précédentes sur le même sujet, voici une liste (que je crois exhaustive) des candidats au rôle de format idéal, ainsi que quelques notes personnelles, et qui assument leur subjectivité, sur chacun. Tous disposent d'une documentation, parfois d'une vraie norme ouverte, et tous ont des mises en œuvre en logiciel libre :

  • XML, normalisé (par le W3C), très répandu, beaucoup de mises en œuvres en logiciel libre dans tous les langages, beaucoup d'experts disponibles, gère proprement Unicode depuis le début.
  • JSON, le seul, semble t-il, qui aie fait l'objet d'un RFC, le RFC 4627. Assez répandu dans le monde Web 2.0.
  • YAML, plus répandu pour des fichiers (par exemple des fichiers de configuration) locaux, je ne crois pas l'avoir souvent rencontré sur le réseau.
  • Les S-expressions, popularisées par le langage Lisp et donc appréciées par les fans de ce langage.
  • ASN.1, d'origine ISO et donc, à juste titre, mal aimé dans le monde Internet. ASN.1 permet de décrire les données, l'encodage exact sur le câble étant laissé à des formats compagnons comme BER ou DER.
  • Les netstrings, conçu par l'inénarrable djb et qui plait donc à ses fans.
  • Les Protocol Buffers, un des plus récents, mais qui a derrière lui tout le poids du géant Google.
  • On peut citer aussi les formats spécifiques à un langage de programmation donné, qui permettent de sérialiser des objets du langage pour les transporter sur le réseau comme pickle pour Python ou java.io.Serializable pour Java.
  • Et, enfin, les informaticiens étant ce qu'ils sont, il y a toute la cohorte des formats conçus et mis en œuvre localement par un programmeur ou bien une petite équipe...

L'article seul

Les protocoles réseaux de bavardage

Première rédaction de cet article le 7 Juin 2009


Il existe une classe de protocoles réseaux nommés protocoles de bavardage (gossip protocol) qui résoud élegamment un problème classique dans les réseaux : informer tout le monde sans utiliser de mass media centralisé. Ils utilisent pour cela un principe simple : tout pair du réseau transmet l'information aux pairs qu'il connait (un sous-ensemble du total) et ceux-ci, à leur tour, transmettent aux pairs qu'ils connaissent, et ainsi de suite (on appelle parfois cette procédure l'indondationflooding). Au bout d'un moment, la totalité du réseau est au courant, sans qu'on aie eu besoin d'un système centralisé.

L'Internet utilise un certain nombre de protocoles de bavardage. C'est ainsi que le routage global, avec le protocole BGP (RFC 4271), dépend d'un protocole de bavardage. Aucun routeur BGP ne connait tous les autres routeurs BGP de la DFZ, l'Internet n'a pas de centre, pas de chaîne de télévision unique qui informerait tous les routeurs de l'arrivée d'un nouveau réseau. Pourtant, de fil en aiguille, l'information sur un nouveau préfixe d'adresses IP touche tout l'Internet en quelques minutes.

Avant BGP, les protocoles de bavardage ont été surtout popularisés par les News (RFC 1036, section 5), où un article était diffusé à chaque pair (par opposition au Web qui est d'avantage centralisé : si le serveur de www.playboy.fr tombe en panne ou bien est victime d'une attaque, l'information (?) sur le site n'est plus accessible du tout). Au contraire, avec BGP ou les News, il faut détruire beaucoup de machines pour réellement supprimer certaines informations ! (Avec les protocoles de bavardage, l'information cesse d'être accessible à tous dès que le réseau cesse d'être connecté, lorsqu'il est partitionné en deux - ou plus - morceaux.)

Donc, les principales forces des protocoles de bavardage sont la résistance aux pannes et le fait qu'aucun nœud ne joue de rôle essentiel, y compris pour l'injection initiale (alors que le terme d'« inondation » n'implique pas d'égalité, l'inondation peut aussi servir lorsqu'un des nœuds est le seul à pouvoir initier une information).

Pour fonctionner correctement, les protocoles de bavardage ont besoin que chaque pair maintienne un historique : quels messages a t-il reçus, a qui les a t-il envoyés, etc. Autrement, on perdrait du temps et des ressources à retransmettre un message déjà connu et on risquerait même de faire des boucles sans fin. Dans les News, chaque serveur retient les messages reçus (identifiés par leur Message-ID:, et « oubliés » au bout d'une durée réglable, pour éviter de remplir le disque dur). BGP a en outre d'autres mesures pour limiter l'inondation comme le fait de ne transmettre comme toutes que celles qu'il utilise lui-même, éliminant ainsi d'une propagation ultérieure les « mauvaises » routes.

Une propriété intéressante des architectures fondées sur le bavardage est qu'il n'est pas nécessaire que la transmission des messages entre les pairs utilise toujours le même protocole. Par exemple, dans les News, le protocole NNTP (RFC 3977) est le plus courant mais pas le seul (pendant longtemps, UUCP était plus utilisé).

Pour illustrer le fonctionnement d'un protocole de bavardage, voici une petite implémentation en Python, avec la bibliothèque standard socket : gossiper.py. Les fils sont utilisés pour éviter d'être bloqué, chaque pair pouvant évidemment être en panne, injoignable, etc. Dans l'implémentation actuelle, on réessaie un nombre limité de fois si un pair n'est pas joignable. Il ne faut évidemment pas comparer cette petite implémentation aux vraies qui tournent sur le vrai Internet, notamment parce qu'elle manque sérieusement de robustesse, par exemple lors de la lecture des messages des autres pairs.

Dans cet exemple simple, le protocole entre deux pairs est fixé (comme avec BGP, mais contrairement aux News). Chaque pair a un numéro unique, l'ID, fixé à la main au démarrage (si on n'a pas de serveur central pour attribuer des numéros uniques, le mieux est de tirer ces ID au hasard dans un espace très grand, limitant ainsi les risques de collision, ou bien de prendre le résumé cryptographique d'une phrase, unique pour chaque pair.) Le « serveur » (le pair qui répond aux demandes de connexion) envoie son ID (suivi d'une virgule). Le « client » (le pair qui lance la connexion) envoie son ID, suivi d'une virgule et suivi du message (sur une seule ligne). Si l'ID est l'identificateur du pair, pour s'y connecter effectivement sur l'Internet, il faut un localisateur, un tuple (adresse IP, port). Dans cette simple implémentation, il n'y a qu'un de ces tuples par pair mais ce n'est pas imposé par le protocole (de toute façon, les pairs sont identifiés par leur ID, pas par leur adresse IP, donc une connexion du pair N peut venir d'une autre adresse IP que celle connue pour N, si la machine cliente a plusieurs adresses).

Au démarrage, on indique donc au programme gossiper.py le port sur lequel il doit écouter (30480 par défaut) et les identificateurs et localisateurs de ses pairs. À noter que la relation entre pair n'est pas symétrique : 1 peut savoir comment joindre 2 sans que le contraire soit vrai. Le nombre de pairs est quelconque mais il faut juste que l'ensemble des pairs soit connecté sinon certains messages n'arriveront pas partout.

Les pairs se souviennent des messagers qu'ils ont vu (dans l'historique global) et de ceux qu'ils ont envoyés à chaque pair (dans un historique spécifique au pair).

Le mode d'emploi est simple. Supposons qu'on veuille lancer le pair d'ID 42, écoutant sur le port 22000, et pouvant parler aux pairs d'ID 261 (2001:DB8:1::bad:dcaf, port par défaut) et d'ID 4231 (2001:DB8:2::dead:beef, port 443), on tapera :

% python gossiper.py -i 42 -p 22000 \
    261,\[2001:DB8:1::bad:dcaf\] 4231,\[2001:DB8:2::dead:beef\]:443

La syntaxe (adresse IP, port) utilisée est celle du RFC 3986, section 3.2.2. Les barres obliques inverses devant les crochets sont imposés par certains shells Unix.

Le programme affichera alors les communications avec ses pairs. Pour amorcer la pompe et injecter des messages facilement, on peut utiliser netcat (ici, l'injecteur original a indiqué un ID de 0) :

% echo 0,"Les sanglots longs des violons de l'automne" | nc 127.0.0.1 22000

Avec des messages inspirés de Radio Londres, on obtiendra des dialogues comme suit (un délai artificiel a été ajouté pour mieux simuler la durée de propagation dans un réseau). Ici, le pair d'ID 5 :

2009-06-05 17:30:13 - ::ffff:127.0.0.1 - 30 bytes (NEW message received from peer 0: "Jean a de longues moustaches...")
2009-06-05 17:30:29 -  - 0 bytes (Sender task Thread-1 received "Jean a de longues moustaches" from 0, connecting to 2 (192.134.7.253:30480))

Et ici le 2 :

2009-06-05 17:31:30 - ::ffff:192.134.4.69 - 30 bytes (NEW message received from peer 5: "Jean a de longues moustaches...")
2009-06-05 17:32:01 -  - 0 bytes (Sender task Thread-3 received "Jean a de longues moustaches" from 5, connecting to 6 (192.134.7.248:4242))

Et ici, un pair, le 3, reçoit un message deux fois, de 6, puis de 2. il ignore le second. Cette précaution est ce qui permet aux protocoles de bavardage de ne pas boucler éternellement.

2009-06-06 22:56:35 2a01:e35:8bd9:8bb0:21c:23ff:fe00:6b7f - 30 bytes - NEW message received from peer 6: "Jean a de longues moustaches..."
2009-06-06 22:56:49 2a01:e35:8bd9:8bb0:21c:23ff:fe00:6b7f - 30 bytes - Ignoring known message from peer 2: "Jean a de longues moustaches..."
...
2009-06-06 22:58:08 2a01:e35:8bd9:8bb0:21c:23ff:fe00:6b7f - 35 bytes - NEW message received from peer 6: "Les lapins s'ennuient le dimanche..."
2009-06-06 22:58:28 2a01:e35:8bd9:8bb0:21c:23ff:fe00:6b7f - 35 bytes - Ignoring known message from peer 2: "Les lapins s'ennuient le dimanche..."

Pour approfondir le concept, on peut lire le bon article du Wikipédia anglophone.


L'article seul

Articles des différentes années : 2009  2008  2007  2006  2005  2004  2003  Précédentes années

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