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.
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)
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 ) :
À 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 :
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 :
fr.wikipedia.org au lieu de http://fr.wikipedia.org/),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 :
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 :
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, message - segment 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.
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.
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...)
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.
Première rédaction de cet article le 22 Juin 2009
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.
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.
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 :
draft-eastlake-dnsext-cookies) ou
EDNS ping (Internet-Draft
draft-hubert-ulevitch-edns-ping),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 :
Merci à Kim Minh Kaplan pour ses nombreuses et pertinentes remarques sur DNSCurve.
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 :
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.
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 :
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.
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.
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.EXAMPLE
où DOMAIN.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.
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 :
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.
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:
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:
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)
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...
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.
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 :
pickle pour
Python ou
java.io.Serializable pour Java.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'indondation - flooding). 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.
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