Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Ève

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


RFC 8222: Selecting Labels for Use with Conventional DNS and Other Resolution Systems in DNS-Based Service Discovery

Date de publication du RFC : Septembre 2017
Auteur(s) du RFC : A. Sullivan (Dyn)
Pour information
Réalisé dans le cadre du groupe de travail IETF dnssd
Première rédaction de cet article le 22 septembre 2017


DNS-SD (DNS-based Service Discovery, normalisé dans le RFC 6763) permet de découvrir des services dans le réseau, via le DNS, mais aussi via d'autres systèmes de résolution de noms, comme mDNS (RFC 6762). Ces différents systèmes de résolution ayant des propriétés différentes, il peut se poser des problèmes d'interopérabilité. Ce RFC documente ces problèmes et est donc une lecture recommandée pour les développeurs DNS-SD.

Rappelons un peu de contexte (section 1 du RFC) : les applications qui utilisent le DNS imposaient fréquemment une syntaxe réduite aux noms manipulés, la syntaxe « LDH » ([ASCII] Letters, Digits and Hyphen), décrite dans le RFC 952. (Contrairement à ce qu'on lit souvent, le DNS n'impose pas une telle limite, elle est purement dans les applications.) Du fait de ces règles des applications, il a fallu, pour utiliser des lettres Unicode, un système spécial, IDN (RFC 5890). IDN ajoute ses propres contraintes, comme le fait que les « non-lettres » (par exemple les emojis) ne sont pas acceptés.

Le DNS accepte tout à fait du binaire quelconque dans les noms (donc, par exemple, des caractères non-ASCII avec des encodages comme Latin-1). mDNS (RFC 6762) autorise également les caractères non-ASCII, mais impose l'encodage UTF-8. mDNS autorise des caractères qui ne sont pas permis en IDN, du banal espace aux symboles les plus rigolos. DNS-SD recommande même leur usage (RFC 6763, section 4.1.3). La seule contrainte est de se limiter au format Unicode du réseau, décrit dans le RFC 5198.

Bien des développeurs d'application ne sont pas au courant de ces subtilités. Ils manipulent des noms de services comme des chaînes de caractères, et ne se soucient pas du système de résolution sous-jacent et de ses particularités. Or, potentiellement, un même nom peut être accepté par certains de ces systèmes de résolution et refusés par d'autres.

Si le développeur est au courant, et s'il prend soin de faire en sorte que les noms « avancés » ne soient pas envoyés aux systèmes de résolution les plus anciens, ça peut marcher. Mais il ne faut pas se faire d'illusion, les fuites se produisent et tout nom utilisé sur une machine connectée à l'Internet finira tôt ou tard par arriver au DNS, comme le montre le trafic que reçoivent les serveurs de noms de la racine.

Notez enfin que les utilisateurs de DNS-SD, encore plus que ceux des noms classiques, ne tapent que très rarement les noms : presque toujours, ils les choisissent dans une liste.

Maintenant, pourquoi y a-t-il un problème ? (Section 2 du RFC.) Comme je l'ai indiqué plus haut, le DNS accepte n'importe quoi dans un nom. Pourquoi ne pas juste dire « envoyons le nom "Joe's printer, first floor" directement dans le DNS, et ça marchera » ? Il y a deux problèmes avec cette approche. D'abord, l'application qui ne ferait pas attention et enverrait un nom non-ASCII en disant « le DNS s'en tirera de toute façon », cette application oublie qu'il peut y avoir sur le trajet de sa requête une couche logicielle qui fait de l'IDN et va donc encoder en Punycode (transformation du U-label en A-label). Le « vrai » système de résolution ne verra donc pas le nom original. Un problème du même genre arrive avec certains logiciels qui se mêlent de politique, par exemple les navigateurs Web comme Firefox qui se permettent d'afficher certains noms en Unicode et d'autres en ASCII, selon la politique du TLD. Bref, le trajet d'un nom dans les différents logiciels est parsemé d'embûches si on est en Unicode.

En outre, l'approche « j'envoie de l'UTF-8 sans me poser de questions » (suggérée par la section 4.1.3 du RFC 6763) se heurte au fait que la racine du DNS et la plupart des TLD ne permettent de toute façon par d'enregistrer directement de l'UTF-8. (Au passage, le RFC oublie un autre problème de l'UTF-8 envoyé directement : les serveurs DNS ne font pas de normalisation Unicode, et l'insensibilité à la casse du DNS n'est pas évidente à traduire en Unicode.)

La section 3 de notre RFC propose donc de travailler à un profil permettant une meilleure interopérabilité. Un profil est une restriction de ce qui est normalement permis (un dénominateur commun), afin de passer à peu près partout. Un exemple de restriction serait l'interdiction des majuscules. En effet, le DNS est insensible à la casse mais IDN interdit les majuscules (RFC 5894, sections 3.1.3 et 4.2) pour éviter le problème de conversion majuscules<->minuscules, qui est bien plus difficile en Unicode qu'en ASCII.

Notez que notre RFC ne décrit pas un tel profil, il propose sa création, et donne quelques idées. Il y a donc encore du travail.

Pour rendre le problème plus amusant, les noms utilisés par DNS-SD sont composés de trois parties, avec des règles différentes (section 4 de notre RFC, et RFC 6763, section 4.1) :

  • « Instance » qui est la partie présentée à l'utilisateur (« Imprimante de Céline »), et où donc tous les caractères sont acceptés,
  • « Service », qui indique le protocole de transport, et qui se limite à un court identificateur technique en ASCII, jamais montré à l'utilisateur et qui, commençant par un tiret bas, n'est pas un nom de machine légal et est donc exclu de tout traitement IDN,
  • « Domaine », qui est le domaine où se trouve l'instance. Il est conçu pour être tôt ou tard envoyé au DNS, et il suit donc les règles IDN.

L'instance risque d'être « interceptée » par le traitement IDN et, à tort, transformée en Punycode. Limiter le nom de l'instance aux caractères acceptés par IDN serait horriblement restrictif. La seule solution est probablement de mettre en place du code spécifique qui reconnait les noms utilisés par DNS-SD, pour ne jamais les passer au DNS. Quant à la partie « Domaine », l'idée de la traiter comme un nom de domaine normal, et donc sujet aux règles des RFC 5890 et suivants, est restrictive :

  • Cela revient à abandonner la stratégie de « on essaie UTF-8, puis Punycode » du RFC 6763, section 4.1.3 (mais cette stratégie a l'inconvénient de multiplier les requêtes, donc le trafic et les délais),
  • Elle ne tient pas compte du fait que certains administrateurs de zones DNS peuvent avoir envie de mettre de l'UTF-8 directement dans la zone (ce qui est techniquement possible, mais va nécessiter de synchroniser dans la zone la représentation UTF-8 et la représentation Unicode du même nom, ou qu'il utilise les DNAME du RFC 6672).

Téléchargez le RFC 8222


L'article seul

Non, BGP ne préfère pas les annonces les plus spécifiques

Première rédaction de cet article le 18 septembre 2017


On lit souvent dans les articles sur la sécurité du routage Internet, ou dans les articles qui décrivent un détournement BGP, volontaire ou non, une phrase du genre « la route pour le préfixe de l'attaquant, un /24, a été acceptée car elle est plus spécifique que le préfixe normal /22, et BGP préfère les routes plus spécifiques ». Ce n'est pas vraiment exact.

La question est importante, car ces détournements BGP arrivent (cf. le rapport de l'ANSSI à ce sujet), le cas le plus célèbre étant Pakistan Telecom contre YouTube, et le plus récent la détournement accidentel par Google, notamment au Japon. Si une organisation annonce un /22, et que l'attaquant publie un /24 mensonger, est-ce que cela aura davantage d'effet que s'il l'attaquant avait publié un /22 ? Et, pour le titulaire légitime du préfixe, est-ce que annoncer des /24 en plus est une bonne défense ? On lit souvent des Oui aux deux questions, avec comme justification que BGP préférerait le préfixe le plus spécifique, et qu'il ne faut donc pas le laisser à l'attaquant. La technique est bonne, mais cette justification n'est pas complètement correcte. (Bien qu'on la trouve même dans des articles sérieux .)

BGP (RFC 4271, notamment la section 9.1), en effet, n'a pas d'opinion sur la longueur des routes. S'il voit un /22 et un /24 plus spécifique, il les considère comme deux préfixes différents, et les garde en mémoire et les passe à ses voisins. (Les experts BGP noteront que j'ai un peu simplifié, BGP ayant plusieurs listes de routes, les RIB - Routing Information Base. La Loc-RIB ne garde que le préfixe spécifique mais l'Adj-RIBs-Out, ce qui est transmis aux voisins, a bien les deux routes.)

C'est IP, pas BGP, qui a une règle « longest prefix » (préfixe le plus spécifique) et qui, lui, choisira le /24. Comme le dit la maxime des opérateurs, « The data plane [IP] does not always follow the control plane [BGP]. »

Vous pouvez facilement voir cela sur un looking glass. Prenons par exemple celui de Level 3, en http://lg.level3.net/. Le préfixe 2405:fd80::/32 a un plus-spécifique, 2405:fd80:100::/40 (il en a même plusieurs, mais je simplifie). Les deux sont transportés par BGP. Le looking glass affiche (j'ai fait deux requêtes séparées, car l'option Longer prefixes de ce looking glass ne marche pas pour moi) :

BGP Routing table entry for 2405:fd80::/32
  3491 135391

...

BGP Routing table entry for 2405:fd80:100::/40
  2914 135391
    

Le routeur connait donc bien les deux préfixes, le générique et le spécifique. (Il s'agit du comportement par défaut de BGP ; il existe des configurations de routeurs qui ne font pas cela, par exemple parce qu'elles agrègent les préfixes les plus spécifiques en un préfixe moins spécifique. Voir par exemple celle de Juniper.)

Mais, si je tape une adresse IP (pas un préfixe) dans un looking glass ou un routeur, je n'obtiens bien que le préfixe plus spécifique ? Oui, mais ce n'est pas un choix de BGP, c'est le choix du routeur sous-jacent de chercher le préfixe immédiatement englobant cette adresse. Tapez un préfixe au lieu d'une adresse IP et vous aurez bien le préfixe demandé.

[Le reste est plus technique, avec du code qui tourne.]

Si vous voulez vous-même analyser la table de routage mondiale, sans recourir à un service cloud comme RIPE Stat, voici deux façons de faire, l'une avec PostgreSQL, et l'autre avec un programme en C. Les deux utilisent la même source de données, RouteViews. RouteViews distribue des mises à jour et des tables complètes (RIB = Routing Information Base). On télécharge la RIB et on la décomprime :

      
% wget http://archive.routeviews.org/bgpdata/2017.09/RIBS/rib.20170905.1200.bz2
% bunzip2 rib.20170905.1200.bz2

    

Elle est au format MRT (RFC 6396). On utilise bgpdump pour la traduire en texte :

    
% bgpdump rib.20170905.1200>rib.20170905.1200.txt

De manière grossière et sauvage, on ne garde que les lignes indiquant un préfixe, et on les dédoublonne (attention, ça va manger de la RAM) :

% grep PREFIX: rib.20170905.1200.txt | sort | uniq > rib.20170905.1200.sorted

À partir de là, on va utiliser soit PostgreSQL, soit le programme en C.

D'abord, avec PostgreSQL, qui a depuis longtemps d'excellents types de données pour les adresses IP, avec plein d'opérations possibles. Pour importer le fichier texte dans PostgreSQL, on utilise un tout petit script Python :

    
% ./dump2psql.py rib.20170905.1200.sorted

Et tout est maintenant dans la base de données, qu'on peut explorer. Par exemple, trouver tous les préfixes plus générique que 192.134.0.0/24 :


% psql bgp
bgp=> SELECT * FROM Prefixes WHERE pfx >> '192.134.0.0/24';
   id    |      pfx       
---------+----------------
 1135437 | 192.134.0.0/16
 1135438 | 192.134.0.0/22
(2 rows)

Et si on veut la liste de toutes les annonces plus spécifiques qu'une annonce existante, ici en IPv6 :

   
bgp=> SELECT gen.pfx AS Generic, spec.pfx AS Specific FROM Prefixes gen,  Prefixes spec
      WHERE family(spec.pfx) = 6 AND  masklen(spec.pfx) < 48 AND spec.pfx << gen.pfx;
       generic       |      specific       
---------------------+---------------------
 2001:1280::/32      | 2001:1280:8000::/36
 2001:128c::/32      | 2001:128c:6000::/36
 2001:128c::/32      | 2001:128c:7000::/40
 2001:12f0::/36      | 2001:12f0:100::/42
...

 

Et si on n'aime pas les SGBD et qu'on veut le faire en C ? On va se servir de la bibliothèque libcidr. Installons :

    
%  wget http://www.over-yonder.net/~fullermd/projects/libcidr/libcidr-1.2.3.tar.xz
% unxz libcidr-1.2.3.tar.xz
% tar xvf libcidr-1.2.3.tar
% cd libcidr-1.2.3
% ./configure
% make
% sudo make install

Puis écrivons un petit programme C qui va lire le fichier texte avec la liste des préfixes et afficher les préfixes plus spécifiques (ou égaux) qu'un préfixe donné (option -s) ou les plus génériques (option -g) :

  
% ./read-mrt rib.20170905.1200.sorted -s 72.249.184.0/21
72.249.184.0/21 (line 700376)
72.249.184.0/24 (line 700377)

% ./read-mrt rib.20170905.1200.sorted -g 72.249.184.0/21
72.249.128.0/18 (line 700369)
72.249.184.0/21 (line 700376)

Merci à Bruno Spiquel pour ses conseils.


L'article seul

RFC 8215: Local-Use IPv4/IPv6 Translation Prefix

Date de publication du RFC : Août 2017
Auteur(s) du RFC : T. Anderson (Redpill Linpro)
Chemin des normes
Première rédaction de cet article le 18 septembre 2017


Ce très court RFC documente la réservation du préfixe IPv6 64:ff9b:1::/48, pour les divers mécanismes de traduction entre IPv6 et IPv4.

L'adressage de ces mécanismes de traduction est décrit dans le RFC 6052. Il réservait le préfixe 64:ff9b::/96, qui s'est avéré insuffisant et est désormais complété par notre RFC 8215.

Ouvrez bien l'œil : ces deux préfixes sont différents, même si leurs quatre premiers octets sont identiques (c'est un problème fréquent en IPv6 : il peut être difficile de distinguer deux adresses du premier coup.)

La section 3 de notre RFC expose le problème : depuis la publication du RFC 6052, plusieurs mécanismes de traduction IPv4<->IPv6 ont été proposés (par exemple dans les RFC 6146 et RFC 7915). Ils ne sont pas inconciliables (ils couvrent parfois des scénarios différents ) et un opérateur peut donc vouloir en déployer plusieurs à la fois. Le seul préfixe 64:ff9b::/96 ne va alors plus suffire. Il faut donc un préfixe plus grand, et dont l'usage n'est pas restreint à une seule technologie de traduction.

Pourquoi 64:ff9b:1::/48, d'ailleurs ? La section 4 répond à la question. La longueur de 48 bits a été choisie pour permettre plusieurs mécanismes de traduction, chacun utilisant un préfixe plus spécifique. Par exemple, si chaque mécanisme utilise un /64, le préfixe réservé pour les englober tous devait être un /48, ou plus général (on se limite aux multiples de 16 bits, car ils permettent que les préfixes se terminent sur un deux-points, facilitant la vie de l'administrateur réseaux qui devra les manipuler).

Ensuite, pourquoi cette valeur 64:ff9b:1:: ? Parce qu'elle est adjacente au 64:ff9b::/96, minimisant la consommation d'adresses IPv6. (64:ff9a:ffff::/48 est également adjacent, de l'autre côté, mais l'agrégation avec 64:ff9b::/96 en un seul préfixe est beaucoup moins efficace. Et puis 64:ff9b:1:: est plus court.)

À part son usage, 64:ff9b:1::/48 est un préfixe IPv6 normal et les routeurs, ou autres machines qui manipulent des adresses IPv6, ne doivent pas le traiter différemment des autres.

Le nouveau préfixe est désormais enregistré dans le registre des adresses IPv6 spéciales (section 7 de notre RFC).


Téléchargez le RFC 8215


L'article seul

RFC 8246: HTTP Immutable Responses

Date de publication du RFC : Septembre 2017
Auteur(s) du RFC : P. McManus (Mozilla)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 16 septembre 2017


Lorsqu'un serveur HTTP renvoie une réponse à un client, il peut indiquer une durée de vie maximale de la ressource transmise, avec l'en-tête Cache-Control: (RFC 7234). Mais cela ne donne qu'une durée maximale. La ressource peut quand même être modifiée avant la fin de cette période. Un client HTTP prudent va donc revalider la fraîcheur de cette ressource de temps en temps. L'extension décrite dans ce RFC permet de se dispenser de cette revalidation, en indiquant une durée minimale, pendant laquelle on est sûrs et certains que la ressource ne sera pas modifiée.

Le RFC prend l'exemple (section 1) d'un journal dont la page d'accueil indique une durée de vie maximale d'une heure :

Cache-Control: max-age=3600
    

Le client HTTP qui reçoit cet en-tête dans la réponse sait qu'il doit recontacter le serveur au bout d'une heure. Mais la page sera peut-être modifiée (par exemple en raison d'un évènement soudain et imprévu) avant qu'une heure soit écoulée. Le client est donc partagé entre la revalidation (qui risque de consommer des ressources pour rien, même si elle est moins coûteuse qu'un téléchargement complet, grâce aux trucs du RFC 7232) et le risque de servir à son utilisateur une ressource qui n'est désormais plus à jour. Le problème est d'autant plus ennuyeux que la revalidation d'une bête page Web qui comprend des photos peut générer beaucoup de requêtes, dont la plupart produiront sans doute un 304 (ressource non modifiée, la revalidation était inutile).

Notez que dans certains cas, le contenu pointé par un URL peut changer, mais dans d'autres cas, il est réellement immuable. Par exemple, si on publie chaque version de sa feuille de style sous un URL différent, la CSS n'a pas besoin d'être revalidée.

Bref, il y avait un besoin de pouvoir indiquer l'immuabilité d'une ressource. C'est désormais fait (section 2 de ce RFC) avec une extension à Cache-Control: :

Cache-Control: max-age=3600, immutable
    

Avec le mot-clé immutable, le serveur indique que la ressource ne sera pas modifiée pendant la durée de vie indiquée, le client peut donc se dispenser de vérifier. (« Client » ici désignant aussi bien le client final, par exemple le navigateur Web, qu'un intermédiaire.)

Par exemple, comme les RFC sont immuables (on ne les change jamais même d'une virgule, même en cas d'erreur), la ressource qui désigne ce RFC, https://www.rfc-editor.org/rfc/rfc8246.txt, pourrait parfaitement renvoyer cet en-tête (elle ne le fait pas). Cela pourrait être :

Last-Modified: Thu, 14 Sep 2017 23:11:35 GMT
Cache-Control: max-age=315576000, immutable 
    

(Oui, dix ans…)

Voilà, c'est tout, la directive a été ajoutée au registre IANA. Mais la section 3 du RFC se penche encore sur quelques questions de sécurité. Indiquer qu'une ressource est immuable revient à la fixer pour un temps potentiellement très long, et cela peut donc servir à pérenniser une attaque. Si un méchant pirate un site Web, et sert son contenu piraté avec un en-tête d'immuabilité, il restera bien plus longtemps dans les caches. Pire, sans HTTPS, l'en-tête avec immutable pourrait être ajouté par un intermédiaire (le RFC déconseille donc de tenir compte de cette option si on n'a pas utilisé HTTPS).

Les navigateurs Web ont souvent deux options pour recharger une page, « douce » et « dure » (F5 et Contrôle-F5 dans Firefox sur Unix). Le RFC conseille que, dans le second cas (rechargement forcé), le immutable soit ignoré (afin de pouvoir recharger une page invalide).

Enfin, toujours question sécurité, le RFC recommande aux clients HTTP de ne tenir compte de l'en-tête d'immuabilité que s'ils sont sûrs que la ressource a été transmise proprement (taille correspondant à Content-Length: par exemple).

Si vous voulez comparer deux ressources avec et sans immutable, regardez http://www.bortzmeyer.org/files/maybemodified.txt (sans immutable) et http://www.bortzmeyer.org/files/forever.txt (avec). Si le client HTTP gère l'extension d'immuabilité, un rechargement « doux » ne fera pas de requête HTTP. Sinon, il y aura revalidation et le serveur HTTP renverra un 304 :

[2001:db8:abcd:1234:acd8:9bd0:27c9:3a7f]:45452 - - [16/Sep/2017:17:53:29 +0200] "GET /files/forever.txt HTTP/1.1" 304 - "-" "Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0" www.bortzmeyer.org
    

Apparemment, Firefox gère cette extension mais, ici, le Firefox était sans doute trop vieux (normalement, cela aurait dû arriver avec la version 49, puisque Mozilla était premier intéressé). Les auteurs de Squid ont annoncé qu'ils géreraient cette extension. Côté serveur, notons que Facebook envoie déjà cette extension pour, par exemple, le code JavaScript (qui est versionné et donc jamais changé) :


% curl -v https://www.facebook.com/rsrc.php/v3iCKe4/y2/l/fr_FR/KzpL-Ycd5sN.js
* Connected to www.facebook.com (2a03:2880:f112:83:face:b00c:0:25de) port 443 (#0)
...
< HTTP/2 200 
< content-type: application/x-javascript; charset=utf-8
< cache-control: public,max-age=31536000,immutable
...

    

Idem pour une mise en œuvre d'IPFS.


Téléchargez le RFC 8246


L'article seul

RFC 8187: Indicating Character Encoding and Language for HTTP Header Field Parameters

Date de publication du RFC : Septembre 2017
Auteur(s) du RFC : J. Reschke (greenbytes)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 14 septembre 2017
Dernière mise à jour le 16 septembre 2017


Les requêtes et réponses du protocole HTTP incluent des en-têtes (comme User-Agent: ou Content-Disposition:) avec des valeurs, qui, il y a longtemps, ne pouvaient se représenter directement qu'avec les caractères du jeu ISO 8859-1, voire seulement avec ASCII (c'était compliqué). Comme MIME, dans le RFC 2231, prévoyait un mécanisme très riche pour encoder les en-têtes du courrier électronique, ce RFC 8187 réutilise ce mécanisme pour HTTP. Pour le corps du message (voir par exemple le RFC 7578), rien ne change.

Cette ancienne restriction à Latin-1 (qui n'est plus d'actualité) vient de la norme HTTP, le RFC 2616, dans sa section 2.2, qui imposait l'usage du RFC 2047 pour les caractères en dehors de ISO 8859-1. Le RFC 7230 a changé cette règle depuis (sa section 3.2) mais pas dans le sens d'une plus grande internationalisation (ISO 8859-1 ne convient qu'aux langues européennes), plutôt en supprimant le privilège d'ISO 8859 et en restreignant à ASCII. Et il ne précise pas vraiment comment faire avec d'autres jeux de caractère comme Unicode. Il ne reste donc que la solution du RFC 2231.

Notre nouveau RFC peut être résumé en disant qu'il spécifie un profil du RFC 2231. Ce profil est décrit en section 3, qui liste les points précisés par rapport au RFC 2231. Tout ce RFC n'est pas utilisé, ainsi le mécanisme en section 3 du RFC 2231, qui permettait des en-têtes de plus grande taille, n'est pas importé (section 3.1 de notre RFC).

En revanche, la section 4 du RFC 2231, qui spécifiait comment indiquer la langue dans laquelle était écrite la valeur d'un en-tête est repris pour les paramètres dans les en-têtes. Ainsi, (section 3.2), voici un en-tête (imaginaire : Information: n'a pas été enregstré), avec un paramètre title traditionnel en pur ASCII :

Information: news; title=Economy

et en voici un avec les possibilités de notre RFC pour permettre les caractères £ et € (« Sterling and euro rates ») :

Information: news; title*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates

Par rapport au RFC 2231 (qui était silencieux sur ce point), un encodage de caractères est décrété obligatoire (c'est bien sûr UTF-8), et il doit donc être géré par tous les logiciels. La mention de l'encodage utilisé est également désormais obligatoire (section 3.2 de notre RFC). La langue elle-même est indiquée par une étiquette, selon la syntaxe du RFC 5646. Du fait de ces possibilités plus riches que celles prévues autrefois pour HTTP, les paramètres qui s'en servent doivent se distinguer, ce qui est fait avec un astérisque avant le signe égal (voir l'exemple ci-dessus). Notez que l'usage de l'astérisque n'est qu'une convention : si on trouve un paramètre inconnu dont le nom se termine par un astérisque, on ne peut pas forcément en déduire qu'il est internationalisé.

La valeur du paramètre inclut donc le jeu de caractères et l'encodage (obligatoire), la langue (facultative, elle n'est pas indiquée dans l'exemple ci-dessus) et la valeur proprement dite.

Voici un exemple incluant la langue, ici l'allemand (code de, la phrase est « Mit der Dummheit kämpfen Götter selbst vergebens », ou « contre la bêtise, les dieux eux-mêmes luttent en vain », tirée de la pièce « La pucelle d'Orléans ») :

Quote: theater; 
    sentence*=UTF-8'de'Mit%20der%20Dummheit%20k%C3%A4mpfen%20G%C3%B6tter%20selbst%20vergebens.

La section 4 couvre ensuite les détails pratiques pour les normes qui décrivent un en-tête qui veut utiliser cette possibilité. Par exemple, la section 4.2 traite des erreurs qu'on peut rencontrer en décodant et suggère que, si deux paramètres identiques sont présents, celui dans le nouveau format prenne le dessus. Par exemple, si on a :

Information: something; title="EURO exchange rates";
               title*=utf-8''%e2%82%ac%20exchange%20rates

le titre est à la fois en ASCII pur et en UTF-8, et c'est cette dernière version qu'il faut utiliser, même si normalement il n'y a qu'un seul paramètre title.

Ces paramètres étendus sont mis en œuvre dans Firefox et Opera ainsi que, dans une certaine mesure, dans Internet Explorer.

Plusieurs en-têtes HTTP se réfèrent formellement à cette façon d'encoder les caractères non-ASCII :

  • Authentication-Control:, dans le RFC 8053 (« For example, a parameter "username" with the value "Renee of France" SHOULD be sent as username="Renee of France". If the value is "Renée of France", it SHOULD be sent as username*=UTF-8''Ren%C3%89e%20of%20France instead »),
  • Authorization: (pour l'authentification HTTP, RFC 7616, avec également un paramètre username pour l'ASCII et username* pour l'encodage défini dans ce RFC),
  • Content-Disposition:, RFC 6266, qui indique sous quel nom enregistrer un fichier et dont le paramètre filename* permet tous les caractères Unicode,
  • Link:, normalisé dans le RFC 5988, où le paramètre title* permet des caractères non-ASCII (title étant pour l'ASCII pur).

Les changements depuis le RFC 5987, sont expliqués dans l'annexe A. Le plus spectaculaire est le retrait d'ISO 8859-1 (Latin-1) de la liste des encodages qui doivent être gérés obligatoirement par le logiciel. Cela fera plaisir aux utilisateurs d'Internet Explorer 9, qui avait déjà abandonné Latin-1. Autrement, rien de crucial dans ces changements. Le texte d'introduction a été refait pour mieux expliquer la situation très complexe concernant la légalité (ou pas) des caractères non-ASCII dans les valeurs d'en-tête.

Si vous voulez voir un exemple, essayez de télécharger le fichier http://www.bortzmeyer.org/files/foobar.txt. Si votre client HTTP gère l'en-tête Content-Disposition: et le paramètre internationalisé filename*, le fichier devrait être enregistré sous le nom föbàr.txt.La configuration d'Apache pour envoyer le Content-Disposition: est :


<Files "foobar.txt">
    Header set Content-Disposition "attachment; filename=foobar.txt; filename*=utf-8''f%%C3%%B6b%%C3%%A0r.txt"
</Files>

Par exemple, Safari ou Firefox enregistrent bien ce fichier sous son nom international.

Ah, et puisque ce RFC parle d'internationalisation, on notera que c'est le premier RFC (à part quelques essais ratés au début) à ne pas comporter que des caractères ASCII. En effet, suivant les principes du RFC 7997, il comporte cinq caractères Unicode : dans les exemples (« Extended notation, using the Unicode character U+00A3 ("£", POUND SIGN) » et « Extended notation, using the Unicode characters U+00A3 ("£", POUND SIGN) and U+20AC ("€", EURO SIGN) »), dans l'adresse (« Münster, NW 48155 ») et dans les noms des contributeurs (« Thanks to Martin Dürst and Frank Ellermann »).


Téléchargez le RFC 8187


L'article seul

RFC 8240: Report from the Internet of Things (IoT) Software Update (IoTSU) Workshop 2016

Date de publication du RFC : Septembre 2017
Auteur(s) du RFC : H. Tschofenig (ARM Limited), S. Farrell (Trinity College Dublin)
Pour information
Première rédaction de cet article le 12 septembre 2017


La mode des objets connectés a mené à l'existence de nombreux « objets », qui sont en fait des ordinateurs (avec leurs problèmes, comme le fait que leur logiciel ait des failles de sécurité), mais qui ne sont pas considérés comme des ordinateurs par leurs propriétaires, et ne sont donc pas gérés (pas d'administrateur système, pas de mises à jour des logiciels). L'attaque contre Dyn du 21 octobre 2016, apparemment menée par ces objets, a bien illustré le risque que cette profusion irresponsable crée. Ce nouveau RFC est le compte-rendu d'un atelier sur la mise à jour des objets connectés, atelier de réflexion qui s'est tenu à Dublin les 13 et 14 juin 2016. Il fait le point sur la (difficile) question.

La question de base était « que peut-on faire pour que ces foutus objets soient mis à jour, au moins pour patcher leurs failles de sécurité ? » L'atelier réuni à Trinity College n'a évidemment pas fourni une réponse parfaite, mais a au moins permis de clarifier le problème. Il me semble toutefois, d'après le compte-rendu, qu'il y avait un gros manque : les droits du propriétaire de l'objet. La plupart des solutions discutées tournaient autour de l'idée de mises à jour systématiques et automatiques du logiciel via les serveurs du vendeur, et les éventuelles conséquences néfastes pour le propriétaire (comme l'arrivée de nouvelles fonctions de surveillance, par exemple) ne sont guère mentionnées.

Aujourd'hui, un grand nombre des machines ayant accès, même indirect, à l'Internet, est composé de trucs qualifiés d'objets, qui ont en commun qu'on ne les appelle pas « ordinateurs » (alors que c'est bien cela qu'ils sont). Ces objets sont très divers, allant d'engins qui ont les capacités matérielles d'un ordinateur (une télévision connectée, par exemple) à des petits machins très contraints (CPU et mémoire limités, batterie à la capacité finie, etc). Le risque que font peser ces objets « irresponsables » (pas gérés, pas supervisés) sur l'Internet est connu depuis longtemps. Le RFC cite l'article de Schneier, « The Internet of Things Is Wildly Insecure And Often Unpatchable », qui tirait la sonnette d'alarme en 2014. Il notait que le logiciel de ces objets n'était déjà plus à jour quand l'objet était sorti de sa boite la première fois (les fabricants d'objets connectés aiment bien utiliser des versions antédiluviennes des bibliothèques). Or, des objets peuvent rester branchés et actifs pendant des années, avec leur logiciel dépassé et jamais mis à jour, plein de failles de sécurité que les craqueurs ne vont pas manquer d'exploiter (cf. le logiciel Mirai).

Donc, le problème est connu depuis des années. Mais il n'est pas facile à résoudre :

  • Le mécanisme de mise à jour peut lui-même être une faille de sécurité. Par exemple, un certain nombre d'objets font la mise à jour de leur logiciel en HTTP, sans signature sur le code récupéré, rendant ainsi une attaque de l'homme du milieu triviale.
  • Le RFC n'en parle guère, mais, même si on vérifie le code récupéré, celui-ci peut être malveillant. De nombreuses compagnies (par exemple Sony) ont déjà délibérement distribué du malware à leurs clients, et sans conséquences pénales. Une mise à jour automatique pourrait par exemple installer une porte dérobée, ou bien mettre du code DRM nouveau.
  • Les problèmes opérationnels ne manquent pas. Par exemple, si les objets vérifient le code récupéré via une signature, et qu'ils comparent à une clé publique qu'ils détiennent, que faire si la clé privée correspondante est perdue ou copiée ? (Cf. « Winks Outage Shows Us How Frustrating Smart Homes Could Be. »)
  • Si les objets se connectent automatiquement au serveur pour chercher des mises à jour, cela pose un sérieux problème de vie privée. (Le RFC ne rappelle pas qu'en outre, la plupart de ces objets sont de terribles espions qu'on installe chez soi.)
  • Pour une société à but lucratif, gérer l'infrastructure de mise à jour (les serveurs, la plate-forme de développement, les mises à jour du code, la gestion des éventuels problèmes) a un coût non nul. Il n'y a aucune motivation financière pour le faire, la sécurité ne rapportant rien. (Le RFC s'arrête à cette constatation, ne mentionnant pas la solution évidente : des règles contraignantes obligeant les entreprises à le faire. Le Dieu Marché ne sera certainement pas une solution.)
  • Si l'entreprise originale défaille, qui va pouvoir et devoir assurer ses mises à jour ? (Repensons à l'ex-chouchou des médias, le lapin Nabaztag, et à son abandon par la société qui l'avait vendu.) Si le RFC ne le mentionne pas, il va de soi que le logiciel libre est la solution au problème du « pouvoir » mais pas à celui du « devoir » (dit autrement, le logiciel libre est nécessaire mais pas suffisant).

Le risque n'est donc pas seulement de l'absence de système de mise à jour. Il peut être aussi d'un système de mise à jour bogué, vulnérable, ou non documenté, donc non maintenable.

Le problème de la mise à jour automatique des logiciels est évidemment ancien, et plusieurs systèmes d'exploitation ont des solutions opérationnelles depuis un certain temps (comme pacman ou aptitude). Le RFC se focalise donc sur les objets les plus contraints, ceux limités dans leurs capacités matérielles, et donc dans les logiciels qu'ils peuvent faire tourner.

Après cette introduction, le RFC fait un peu de terminologie, car le choix des mots a suscité une discussion à l'atelier. D'abord, la notion de classe. Les « objets connectés » vont de systèmes qui ont les capacités matérielles et l'alimentation électrique d'un ordinateur de bureau (une télévision connectée, par exemple) et qui peuvent donc utiliser les mêmes techniques, jusqu'à des objets bien plus contraints dans leurs capacités. Pour prendre un exemple chez les systèmes populaires auprès des geeks, un Raspberry Pi fait tourner un système d'exploitation « normal » et se met à jour comme un ordinateur de bureau, un Arduino ne le peut typiquement pas. Il faudra donc sans doute développer des solutions différentes selon la classe. Ou, si on veut classer selon le type de processeur, on peut séparer les objets ayant plus ou moins l'équivalent d'un Cortex-A (comme le Pi) de ceux ayant plutôt les ressources du Cortex-M (comme l'Arduino, mais notez que cette catégorie est elle-même très variée).

Le RFC définit également dans cette section 2 la différence entre mise à jour du logiciel et mise à jour du firmware. La distinction importante est que les engins les plus contraints en ressources n'ont typiquement qu'une mise à jour du firmware, qui change tout, système et applications, alors que les engins de classe « supérieure » ont des mises à jour modulaires (on peut ne mettre à jour qu'une seule application).

Dernier terme à retenir, hitless (« sans impact »). C'est une propriété des mises à jour qui ne gênent pas le fonctionnement normal. Par exemple, s'il faut arrêter l'engin pour une mise à jour, elle ne sera pas hitless. Évidemment, la mise à jour du logiciel d'une voiture ne sera probablement pas hitless et nécessitera donc des précautions particulières.

Maintenant, le gros morceau du RFC, la section 3, qui regroupe notamment les exigences issues de l'atelier. C'est assez dans le désordre, et il y a davantage de questions que de réponses. Pour commencer, que devraient faire les fabricants d'objets connectés (en admettant qu'ils lisent les RFC et aient le sens des responsabilités, deux paris hasardeux) ? Par exemple, le RFC note que les mises à jour globales (on remplace tout) sont dangereuses et recommande que des mises à jour partielles soient possibles, notamment pour limiter le débit utilisé sur le réseau (comme avec bsdiff ou courgette). Idéalement, on devrait pouvoir mettre à jour une seule bibliothèque, mais cela nécessiterait du liage dynamique et de code indépendant de la position, ce qui peut être trop demander pour les plus petits objets. Mais au moins un système d'exploitation plutôt conçu pour les « classe M », les objets les plus contraints (équivalents au Cortex-M), le système Contiki, a bien un lieur dynamique.

Certains dispositifs industriels ont plusieurs processeurs, dont un seul gère la connexion avec le reste du monde. Il peut être préférable d'avoir une architecture où ce processeur s'occupe de la mise à jour du logiciel de ses collègues qui ne sont pas directement connectés.

Un problème plus gênant, car non exclusivement technique, est celui des engins qui ont plusieurs responsables potentiels. Si une machine sert à plusieurs fonctions, on risque d'avoir des cas où il n'est pas évident de savoir quel département de l'entreprise a « le dernier mot » en matière de mise à jour. Pour les ordinateurs, cette question sociale a été réglée depuis longtemps dans la plupart des organisations (par exemple en laissant le service informatique seul maître) mais les objets connectés redistribuent les cartes. Si le service Communication a une caméra connectée, qui peut décider de la mettre à jour (ou pas) ?

Un cas proche est celui où un appareil industriel inclut un ordinateur acheté à un tiers (avec le système d'exploitation). Qui doit faire la mise à jour ? Qui est responsable ? Pensons par exemple au problème d'Android où les mises à jour doivent circuler depuis Google vers le constructeur du téléphone, puis (souvent) vers l'opérateur qui l'a vendu. En pratique, on constate qu'Android est mal mis à jour. Et ce sera pire pour un réfrigérateur connecté puisque sa partie informatique sera fabriquée indépendamment, et avant le frigo complet. Lorsque celui-ci démarrera pour la première fois, son logiciel aura sans doute déjà plusieurs mois de retard.

Et la sécurité ? Imaginons un objet connecté dont le fabricant soit assez incompétent et assez irresponsable pour que les mises à jour se fassent en récupérant du code en HTTP, sans aucune authentification d'aucune sorte. Un tel objet serait très vulnérable à des attaques visant à remplacer le code authentique par un code malveillant. D'où l'exigence de DAO (Data Origin Authentication). Sans DAO, tout serait fichu. Deux solutions évidentes utilisent la cryptographie, authentifier le serveur qui distribue les mises à jour (par exemple avec HTTPS) ou bien authentifier le code téléchargé, via une signature numérique (notez que l'IETF a un format CMS pour cela, décrit dans le RFC 4108, mais qui semble peu utilisé.) Mais signer et vérifier est plus facile à dire qu'à faire ! D'abord, si la cryptographie n'est pas un problème pour les engins « classe A », comme une télévision connectée ou bien une voiture connectée, elle peut être bien coûteuse pour des objets plus limités. La cryptographie symétrique est typiquement moins coûteuse, mais bien moins pratique. Le problème de fond est que la cryptographie symétrique n'authentifie pas un envoyeur mais une classe d'envoyeurs (tous ceux qui ont la clé). Il faudrait donc une clé différente par objet, ce qui semble ingérable. Faut-il lister comme exigence de base d'utiliser la cryptographie asymétrique ?

Son coût en opérations de calcul n'est pas le seul problème. Par exemple, la clé privée qui signe les mises à jour peut être volée, ou bien tout simplement devenir trop faible avec les progrès de la cryptanalyse (certains objets peuvent rester en production pendans dix ans ou plus). Il faut donc un moyen de mettre la clé publique à jour. Comment le faire sans introduire de nouvelles vulnérabilités (imaginez un attaquant qui trouverait le moyen de subvertir ce mécanisme de remplacement de clé, et changerait la clé de signature pour la sienne). Bref, les méthodes « il n'y a qu'à… » du genre « il n'y a qu'à utiliser des signatures cryptographiques » ne sont pas des solutions magiques.

Et si l'objet utilise du code venant de plusieurs sources ? Faut-il plusieurs clés, et des règles compliquées du genre « l'organisation A est autorisée à mettre à jour les composants 1, 3 et 4 du système, l'organisation B peut mettre à jour 2 et 3 » ? (Les systèmes fermés sont certainement mauvais pour l'utilisateur, mais présentent des avantages en matière de sécurité : une seule source. Pensez à tous les débats autour de l'utilisation du magasin libre F-Droid, par exemple l'opinion de Whisper.)

Un problème plus fondamental est celui de la confiance : à qui un objet connecté doit-il faire confiance, et donc quelle clé publique utiliser pour vérifier les mises à jour ? Faire confiance à son propriétaire ? À son fabricant ? Au sous-traitant du fabricant ?

On n'en a pas fini avec les problèmes, loin de là. Les objets sont conçus pour être fabriqués et distribués en grande quantité. Cela va donc être un difficile problème de s'assurer que tous les objets dont on est responsable soient à jour. Imaginez une usine qui a des centaines, voire des milliers d'objets identiques, et qui veut savoir s'ils sont tous à la même version du logiciel.

On a parlé plus haut de confiance. On peut décider de faire confiance au fabricant d'un objet, parce qu'on juge l'organisation en question honnête et compétente. Mais cette évaluation ne s'étend pas forcément à la totalité de ses employés. Comment s'assurer qu'un employé méchant ne va pas compromettre le processus de mise à jour du logiciel, par exemple en se gardant une copie de la clé privée, ou bien en ajoutant aux objets produits une clé publique supplémentaire (il aurait alors une porte dérobée dans chaque objet, ce qui illustre bien le fait que la mise à jour est elle-même source de vulnérabilités).

On a parlé des risques pour la sécurité de l'objet connecté. Les précautions prises sont actuellement proches de zéro. Espérons que cela va s'améliorer. Mais, même en prenant beaucoup de précautions, des piratages se produiront parfois. Ce qui ouvre la question de la récupération : comment récupérer un objet piraté ? Si on a dix grille-pains compromis, va t-il falloir les reflasher tous les dix, manuellement ? (Avant cela, il y a aussi l'intéressante question de savoir comment détecter qu'il y a eu piratage, sachant que la quasi-totalité des organisations ne lisent pas les messages leur signalant des comportements suspects de leurs machines.)

La cryptographie apporte clairement des solutions intéressantes à bien des problèmes de sécurité. Mais elle nécessite en général une horloge bien à l'heure (par exemple pour vérifier la date d'expiration d'un certificat). Avoir une horloge maintenue par une batterie pendant l'arrêt de la machine et pendant les coupures de courant n'est pas trivial :

  • Cela coûte cher (la batterie et l'horloge peuvent coûter bien plus cher que le reste de l'objet),
  • C'est encombrant (certains objets sont vraiment petits),
  • Les batteries sont des équipements dangereux, en raison du risque d'incendie ou d'explosion, et leur présence impose donc de fortes contraintes au processus de fabrication, d'autant plus que les batteries vieillissent vite (pas question d'utiliser de vieux stocks),
  • Les batteries conçues pour la maison peuvent ne pas être adaptées, car, dans un environnement industriel, les conditions sont rudes (la température peut facilement excéder les limites d'une batterie ordinaire),
  • Cela ne dure pas longtemps : la présence d'une batterie peut sérieusement raccourcir la durée de vie d'un objet connecté.

Il n'est donc pas évident qu'il y ait une bonne solution à ce problème. (Le RFC cite Roughtime comme une approche prometteuse.)

Et si on a une bonne solution pour distribuer les mises à jour à des milliers ou des millions d'objets connectés, comment ceux-ci vont-ils être informés de l'existence d'une mise à jour. Push ou pull ? Envoyer les mises à jour depuis le serveur est la solution la plus rapide mais elle peut être délicate si l'objet est derrière un pare-feu qui interdit les connexions entrantes (il faudrait que l'objet contacte le serveur de mise à jour, et reste connecté). Et le cas d'objets qui ne sont pas joignables en permanence complique les choses. De l'autre côté, le cas où l'objet contacte le serveur de temps en temps pour savoir s'il y a une mise à jour pose d'autres problèmes. Par exemple, cela consomme de l'électricité « pour rien ».

Vous trouvez qu'il y a assez de problèmes ? Mais ce n'est pas fini. Imaginez le cas d'une mise à jour qui contienne une bogue, ce qui arrivera inévitablement. Comment revenir en arrière ? Refaire une mise à jour (la bogue risque d'empêcher ce processus) ? L'idéal serait que l'objet stocke deux ou trois versions antérieures de son logiciel localement, pour pouvoir basculer vers ces vieilles versions en cas de problème. Mais cela consomme de l'espace de stockage, qui est très limité pour la plupart des objets connectés.

Le consensus de l'atelier a été que les signatures des logiciels (avec vérification par l'objet), et la possibilité de mises à jour partielles, étaient importants. De même, les participants estimaient essentiels la disponibilité d'une infrastructure de mise à jour, qui puisse fonctionner pour des objets qui n'ont pas un accès complet à l'Internet. Par exemple, il faut qu'une organisation puisse avoir une copie locale du serveur de mise à jour, à la fois pour des raisons de performances, de protection de la vie privée, et pour éviter de donner un accès Internet à ses objets.

En parlant de vie privée (RFC 6973), il faut noter que des objets se connectant à un serveur de mise à jour exposent un certain nombre de choses au serveur, ce qui peut ne pas être souhaitable. Et certains objets sont étroitement associés à un utilisateur (tous les gadgets à la maison), aggravant le problème. Le RFC note qu'au minimum, il ne faut pas envoyer un identificateur unique trop facilement, surtout au dessus d'un lien non chiffré. Mais le RFC note à juste titre qu'il y a un conflit entre la vie privée et le désir des vendeurs de se faire de l'argent avec les données des utilisateurs.

On a surtout parlé jusqu'à présent des risques pour l'objet si le serveur des mises à jour est méchant ou piraté. Certains acteurs du domaine vont ajouter des risques pour le serveur : par exemple, si le logiciel est non-libre, certains peuvent souhaiter authentifier l'objet, pour ne pas distribuer leur précieux logiciel privateur à « n'importe qui ».

On en a déjà parlé mais cela vaut la peine d'y revenir : un gros problème de fond est celui de l'autorisation (section 4 du RFC). Qui peut accepter ou refuser une mise à jour ? Oui, il y a de bonnes raisons de refuser : des mises à jour obligatoires sont une forme de porte dérobée, elles permettent d'ajouter des fonctions qui n'étaient pas là avant et qui peuvent être malveillantes. Le cas avait été cité lors de l'affaire de l'iPhone de San Bernardino (des gens proposaient une mise à jour que le téléphone appliquerait, introduisant une porte dérobée). Et le RFC rappelle le cas d'une mise à jour forcée d'imprimantes HP qui avait supprimé des fonctions utiles mais que HP ne voulait plus (la possibilité d'accepter des cartouches d'encre fournies par les concurrents). Un cas analogue révélé la veille de la publication du RFC est celui de Tesla activant une nouvelle fonction à distance (ici, pour améliorer la voiture, mais il est facile de voir que cela pourrait être en sens inverse). Et il y a bien sûr le cas fameux d'Amazon détruisant des livres à distance.

Prenez un téléphone Android programmé par Google, fabriqué par LG, vendu par Orange à la société Michu qui le confie à son employé M. Dupuis-Morizeau. Qui va décider des mises à jour ?

On peut séparer les mises à jour qui corrigent une faille de sécurité et les autres : seules les premières seraient systématiquement appliquées. (Tous les fournisseurs de logiciel ne font pas cette séparation, qui les oblige à gérer deux lignes de mises à jour.) Ceci dit, en pratique, la distinction n'est pas toujours facile, et la correction d'une faille peut en entrainer d'autres.

D'autre part, certains objets connectés sont utilisés dans un environnement très régulé, où tout, matériel et logiciel, doit subir un processus de validation formel avant d'être déployé (c'est le cas des objets utilisés dans les hôpitaux, par exemple). Il y aura forcément une tension entre « faire la mise à jour en urgence car elle corrige une vulnérabilité » et « ne pas faire la mise à jour avant d'avoir tout revalidé, car cet objet est utilisé pour des fonctions vitales ».

Autre problème rigolo, et qui a de quoi inquiéter, le risque élevé d'une cessation du service de mise à jour logicielle (section 5 du RFC). Les vendeurs de logiciel ne sont pas un service public : ils peuvent décider d'arrêter de fournir un service pour des produits qui ne les intéressent plus (ou qui fait concurrence à un produit plus récent, qui offre une meilleure marge). Ou bien ils peuvent tout simplement faire faillite. Que faire face à cet échec du capitalisme à gérer les produits qu'il met sur le marché ? Le problème est d'autant plus fréquent que des objets connectés peuvent rester en service des années, une éternité pour les directions commerciales. Outre le cas du Nabaztag cité plus haut, le RFC mentionne l'exemple d'Eyefi. Un objet un peu complexe peut incorporer des parties faites par des organisations très différentes, dont certaines seront plus stables que d'autres.

Faut-il envisager un service de reprise de la maintenance, par des organisations spécialisées dans ce ramassage des orphelins ? Si ces organisations sont des entreprises à but lucratif, quel sera leur modèle d'affaires ? (Le RFC ne mentionne pas la possibilité qu'il puisse s'agir d'un service public.) Et est-ce que l'entreprise qui abandonne un produit va accepter ce transfert de responsabilité (aucune loi ne la force à gérer ses déchets logiciels) ?

Pour le logiciel, une grosse différence apparait selon qu'il s'agisse de logiciel libre ou pas. Un projet de logiciel libre peut se casser la figure (par exemple parce que l'unique développeur en a eu marre), mais il peut, techniquement et juridiquement, être repris par quelqu'un d'autre. Ce n'est qu'une possibilité, pas une certitude, et des tas de projets utiles ont été abandonnés car personne ne les a repris.

Néanmoins, si le logiciel libre n'est pas une condition suffisante pour assurer une maintenance sur le long terme, c'est à mon avis une condition nécessaire. Parfois, un logiciel est libéré lorsqu'une société abandonne un produit (ce fut le cas avec Little Printer, qui fut un succès, la maintenance étant repris par des développeurs individuels), parfois la société ne fait rien, et le logiciel est perdu. Bref, un système de séquestre du code, pour pouvoir être transmis en cas de défaillance de l'entreprise originale, serait une bonne chose.

Comme souvent en sécurité, domaine qui ne supporte pas le « ya ka fo kon », certaines exigences sont contradictoires. Par exemple, on veut sécuriser les mises à jour (pour éviter qu'un pirate ne glisse de fausses mises à jour dans le processus), ce qui impose des contrôles, par exemple par des signatures numériques. Mais on veut aussi assurer la maintenance après la fin de la société originelle. Si un prestataire reprend la maintenance du code, mais qu'il n'a pas la clé privée permettant de signer les mises à jour, les objets refuseront celles-ci… Le séquestre obligatoire de ces clés peut être une solution, mais elle introduit d'autres risques (vol des clés chez le notaire).

Une solution possible serait que les appareils connectés soient programmés pour cesser tout service s'ils n'arrivent pas à faire de mise à jour pendant N mois, ou bien si le serveur des mises à jour leur dit que le contrat de support est terminé. De telles bombes temporelles seraient bonnes pour la sécurité, et pour le business des fabricants. Mais elles changeraient le modèle d'achat d'équipement : on ne serait plus propriétaire de sa voiture ou de son grille-pain, mais locataire temporaire. De la vraie obsolescence programmée ! De telles bombes dormantes peuvent aussi interférer avec d'autres fonctions de sécurité. Par exemple, un téléphone doit pouvoir passer des appels d'urgence, même si l'abonnement a expiré. Il serait logique qu'il soit en mesure d'appeler le 112, même si son logiciel n'a pas pu être mis à jour depuis longtemps (cf. RFC 7406 pour une discussion sur un compromis similaire.)

Dans une logique libertarienne, ce RFC parle peu de la possibilité d'obligations légales (comme il y a un contrôle technique pour les voitures, il pourrait y avoir obligation de mesures de sécurité quand on vend des objets connectés). Comme il n'y a aucune chance que des entreprises capitalistes fassent spontanément des efforts pour améliorer la sécurité de tous, si on n'utilise pas de contraintes légales, il reste la possibilité d'incitations, discutée dans la section 6. Certaines sont utopiques (« il est dans l'intérêt de tous d'améliorer la sécurité, donc il faut expliquer aux gentils capitalistes l'intérêt de la sécurité, et ils le feront gratuitement »). Une possibilité est celle d'un abonnement : ayant acheté un objet connecté, on devrait payer régulièrement pour le maintenir à jour. Du point de vue de la sécurité, c'est absurde : il est dans l'intérêt de tous que toutes les machines soient à jour, question sécurité. Avec un abonnement, certains propriétaires choisiront de ne pas payer, et leurs engins seront un danger pour tous. (Comparons avec la santé publiqué : il est dans l'intérêt des riches que les pauvres se soignent, sinon, les maladies contagieuses se répandront et frapperont tout le monde. Les vaccinations gratuites ne sont donc pas de la pure générosité.)

Un des problèmes du soi-disant « Internet des objets » est qu'on ne sait pas grand'chose de ce qui s'y passe. Combien de constructeurs d'objets connectés peuvent dire combien ils ont d'objets encore actifs dans la nature, et quel pourcentage est à jour, question logiciel ? La section 7 du RFC se penche sur les questions de mesures. Un des articles présentés à l'atelier notait que, treize ans après la fin de la distribution d'un objet connecté, on en détectait encore des exemplaires actifs.

Garder trace de ces machines n'est pas trivial, puisque certaines ne seront allumées que de manière très intermittente, et que d'autres seront déployées dans des réseaux fermés. Mais ce serait pourtant utile, par exemple pour savoir combien de temps encore il faut gérer des mises à jour pour tel modèle, savoir quels problèmes ont été rencontrés sur le terrain, savoir quelles machines ont été compromises, etc.

La solution simple serait que l'objet ait un identifiant unique et contacte son fabricant de temps en temps, en donnant des informations sur lui-même. Évidemment, cela soulève de gros problèmes de préservation de la vie privée, si le fabricant de brosses à dents connait vos habitudes d'hygiène (« sa brosse à dents n'a pas été allumée depuis six mois, envoyons-lui des pubs pour un dentiste »). Il existe des techniques statistiques qui permettent de récolter des données sans tout révéler, comme le système RAPPOR ou EPID.

Quelle que soit l'efficacité des mises à jour, il ne fait pas de doute que, parfois, des machines seront piratées. Comment les gérer, à partir de là (section 9) ? Car ce ne sera pas seulement un ou deux objets qu'il faudra reformater avec une copie neuve du système d'exploitation. En cas de faille exploitée, ce seront au moins des centaines ou des milliers d'objets qui auront été piratés par un logiciel comme Mirai. (Notez qu'un travail - qui a échoué - avait été fait à l'IETF sur la gestion des machines terminales et leur sécurité, NEA.)

Rien que signaler à l'utilisateur, sans ouvrir une nouvelle voie d'attaque pour le hameçonnage, n'est pas évident (le RFC 6561 propose une solution, mais peu déployée). Bref, pour l'instant, pas de solution à ce problème. Quand on sait que les organisations à but lucratif ne réagissent jamais quand on leur signale un bot dans leur réseau, on imagine ce que ce sera quand on préviendra M. Michu que son grille-pain est piraté et participe à des DoS (comme celles avec SNMP ou bien celle contre Brian Krebs).

Avant d'arriver à une conclusion très partielle, notre RFC, dans sa section 10, traite quelques points divers :

  • Pas mal de gadgets connectés seront rapidement abandonnés, ne délivrant plus de service utile et n'intéressant plus leur propriétaire, mais dont certains seront toujours connectés et piratables. Qui s'occupera de ces orphelins dangereux ?
  • Que devra faire la machine qui voit qu'elle n'arrive pas à faire ses mises à jour ? Couiner très fort comme le détecteur de fumée dont la batterie s'épuise ? Vous imaginez votre cuisine quand dix appareils hurleront en même temps « Software update failed. Act now! » ? Ou bien les appareils doivent-ils s'éteindre automatiquement et silencieusement ? (Les fabricants adoreront cette solution : pour éliminer les vieilles machines et forcer les clients à acheter les nouvelles, il suffira de stopper le serveur des mises à jour.)
  • La cryptographie post-quantique est pour l'instant inutile (et les mécanismes actuels font des signatures énormes, inadaptées aux objets contraints) mais il est impossible de dire quand elle deviendra indispensable. Cinq ans ? Dix ans ? Jamais ? Le problème est que, les objets connectés restant longtemps sur le terrain, il y a un risque qu'un objet soit livré avec de la cryptographie pré-quantique qui soit cassée par les ordinateurs quantiques pendant sa vie. On sera alors bien embêtés.
  • Enfin, pour ceux qui utilisent des certificats pour vérifier les signatures, il faut noter que peu testent les révocations (RFC 6961 et ses copains). Il sera donc difficile, voire impossible, de rattraper le coup si une clé privée de signature est volée.

La section 11 rassemble les conclusions de l'atelier. On notera qu'il y a peu de consensus.

  • Comme c'est l'IETF, il n'est pas étonnant que les participants soient tombés d'accord sur l'utilité d'une solution normalisée pour la distribution et la signature de logiciels, afin de permettre des mises à jour fiables.
  • Il n'est par contre pas clair de savoir si cette solution doit tout inclure, ou seulement certaines parties du système (la signature, par exemple).
  • Des mécanismes de « transmission du pouvoir » sont nécessaires, par exemple pour traiter le cas où une société fait faillite, et passe la maintenance des objets qu'elle a vendu à une association ou un service public. Passer le mot de passe de root est un exemple (très primitif).

L'annexe B du RFC liste tous les papiers présentés à l'atelier qui sont, évidemment en ligne. Sinon, vous pouvez regarder le site officiel de l'atelier, et un bon résumé de présentation. Je recommande aussi cet article de l'IETF Journal, et un très bon article critique sur les objets connectés.


Téléchargez le RFC 8240


L'article seul

Annonces BGP plus spécifiques : bien ou mal ?

Première rédaction de cet article le 4 septembre 2017


L'article de Geoff Huston « BGP More Specifics: Routing Vandalism or Useful? » aborde une question qui a toujours déclenché de chaudes discussions dans les forums d'opérateurs réseaux : est-ce que les opérateurs qui, au lieu d'annoncer un et un seul préfixe IP très général, annoncent plusieurs sous-préfixes, sont des salauds ou pas ? Est-ce l'équivalent de polluer et de taguer ? Après une étude soignée et pas mal de graphiques, il apporte une réponse originale.

Comme d'habitude, je vais commencer par un petit rappel. Le protocole BGP, normalisé dans le RFC 4271, est le protocole standard de distribution des routes dans l'Internet. Une route est annoncée sous forme d'un préfixe IP, par exemple 2001:db8::/32, et les routeurs avec qui on communique relaieront cette annnonce, jusqu'à ce que tout l'Internet soit au courant. Le préfixe annoncé inclut une longueur (32 bits dans l'exemple ci-dessus). Comme des expressions comme « grand préfixe » sont ambiguës (parle-t-on de la longueur du préfixe, ou bien du nombre d'adresses IP qu'il contient ?), on parle de préfixes plus spécifiques (longueur plus grande, moins d'adresses IP) ou moins spécifiques (longueur plus réduite, davantage d'adresses IP). Ainsi, 2001:db8:42:abcd::/64 est plus spécifique que 2001:db8:42::/48.

Si un opérateur a le préfixe 2001:db8::/32, et qu'il annonce en BGP non seulement ce préfixe mais également 2001:db8:42::/48 et 2001:db8:cafe::/48, on dit qu'il annonce des préfixes plus spécifiques. Comme toute route va devoir être stockée dans tous les routeurs de la DFZ, cet opérateur qui annonce trois routes au lieu d'une seule est vu par certains comme un gougnafier qui abuse du système (et, là, on cite en général la fameuse « Tragedy of the Commons »). Cet opérateur force tous les routeurs de la DFZ à consacrer des ressources (CPU, mémoire) à ses trois préfixes, et cela gratuitement, alors qu'il pourrait se contenter d'un seul préfixe, qui englobe les trois en question.

Le problème est d'autant plus complexe que l'Internet est un réseau décentralisé, sans chef et sans police, et que personne ne peut faire respecter des règles mondiales. Donc, même si on pense que les plus spécifiques sont une mauvaise chose, comment les interdire en pratique ?

En général, les opérateurs estiment que les annonces plus spécifiques sont une mauvaise chose. Néanmoins, pas mal de gens en font. Huston regarde de plus près pourquoi, et quelles sont les conséquences. Les plus spécifiques peuvent être utiles pour leur émetteur, car la transmission de paquets IP est fondée sur le principe du « préfixe le plus spécifique » : si un routeur IP a deux routes pour la destination 2001:db8:cafe::1:443, la première pour le préfixe 2001:db8::/32, et la seconde pour le préfixe 2001:db8:cafe::/48, il utilisera toujours la seconde, la plus spécifique. (Si, par contre, il doit envoyer un paquet à 2001:db8:1337::bad:dcaf, il utilisera la première route, puisque la seconde ne couvre pas cette destination.) L'utilisation de préfixes plus spécifiques peut donc être un moyen de déterminer plus finement le trajet qu'emprunteront les paquets qui viennent vers vous.

Quel est le pourcentage de ces « plus spécifiques », d'ailleurs ? Huston mesure environ 50 % des routes IPv4 et 40 % en IPv6. C'est donc une proportion non négligeable. La question est « peut-on / doit-on en réduire le nombre ? Est-ce possible ? Quels sont les coûts et les bénéfices ? »

Pour répondre à cette question, Huston développe une taxonomie des annonces plus spécifiques, avec des exemples réels. (Les exemples ci-dessous ont été vérifiés mais attention, le looking glass typique n'indique que le préfixe le plus spécifique ; si on n'a pas soi-même une copie de la DFZ et les outils pour l'analyser, il faut utiliser un service qui affiche également les préfixes englobants - je me sers de RIPE Stat et un looking glass - j'utilise celui de Hurricane Electric.) Huston distingue trois cas. Le premier est celui de « percement d'un trou » : l'annonce plus générale n'envoie pas au bon AS et on fait donc une annonce plus spécifique avec un AS d'origine différent. On voit ainsi, pour 72.249.184.0/24, sur RIPE Stat :

Originated by: AS394094 (valid route objects in LEVEL3 and RADB)

Covering less-specific prefixes:
72.249.128.0/18 (announced by AS30496)
72.249.184.0/21 (announced by AS36024)

Showing results for 72.249.184.0/24 as of 2017-09-03 08:00:00 UTC
    

Le /21 est annoncé par l'AS 36024 alors qu'il y a une annonce plus spécifique pour le /24, avec un autre AS, 394094. Huston note que cela pourrait être fait en annonçant un préfixe par /24 mais cela créerait davantage d'entrées dans la table de routage que ce percement de trou avec un plus spécifique.

Le second cas d'annonce plus spécifique est l'ingénierie de trafic. L'AS d'origine est le même, mais l'annonce est différente sur un autre point, par exemple par le prepending (la répétition du même AS dans l'annonce, pour rendre le chemin plus long et donc décourager l'usage de cette route), ou bien en envoyant les deux préfixes, le plus spécifique et le moins spécifique, à des opérateurs différents :

    
Originated by: AS4775 (valid route objects in RADB and NTTCOM)

Covering less-specific prefix: 1.37.0.0/16 (announced by AS4775)

Showing results for 1.37.27.0/24 as of 2017-09-03 08:00:00 UTC

Cette fois, l'AS d'origine est le même. Mais on note sur le looking glass que les annonces sont différentes :

Prefix: 1.37.0.0/16
          AS_PATH: 4766 4775

Prefix: 1.37.27.0/24
         AS_PATH: 4775	  

Le préfixe plus spécifique n'est pas annoncé aux mêmes opérateurs.

Enfin, le troisième et dernier cas est celui du recouvrement complet : l'annonce plus spécifique est apparemment inutile puisque il existe une annonce moins spécifique qui a exactement les mêmes caractéristiques. Pourquoi ferait-on cela ? Il peut s'agir de négligence ou d'incompétence mais il existe aussi une raison rationnelle : limiter les conséquences d'un détournement BGP.

Dans un tel détournement, l'attaquant annonce un préfixe qui n'est pas le sien, pour perturber l'accès à la victime, ou pour détourner son trafic vers l'attaquant (les cas les plus connus sont accidentels, comme le fameux détournement de YouTube par le Pakistan, mais cela peut aussi se faire délibérement). Si l'annonce normale est un /22, et que l'attaquant annonce aussi un /22, une partie de l'Internet verra toujours l'annonce légitime (les routeurs BGP ne propagent, pour un préfixe donné, que la meilleure route) et ne sera donc pas affectée. Pour qu'une attaque réussisse bien, dans ces conditions, il faut que l'attaquant soit très bien connecté, avec de nombreux partenaires BGP. Par contre, avec cette annonce légitime en /22, un attaquant qui enverrait une annonce pour un /24 verrait son annonce propagée partout (puisqu'il s'agirait d'un préfixe différent). Et, au moment de la transmission des paquets, les routeurs utiliseront la route la plus spécifique, donc le /24. Ainsi, un attaquant mal connecté pourra toujours voir son annonce acceptée et utilisée dans tout l'Internet.

Le fait d'annoncer un recouvrement complet (quatre /24 en plus du /22) protège donc partiellement contre cette technique. Huston rejette cet argument en disant qu'il ne s'agit que d'une protection partielle, mais je ne suis pas d'accord : une protection partielle vaut mieux que pas de protection du tout et, en sécurité, il est courant que les solutions soient partielles.

Un exemple est 1.0.4.0/22. On voit les quatre préfixes plus spécifiques, avec exactement le meme contenu :

Prefix: 1.0.4.0/22
          AS_PATH: 4826 38803 56203

Prefix: 1.0.4.0/24
         AS_PATH: 4826 38803 56203
Prefix: 1.0.5.0/24
         AS_PATH: 4826 38803 56203
Prefix: 1.0.6.0/24
         AS_PATH: 4826 38803 56203
Prefix: 1.0.7.0/24
         AS_PATH: 4826 38803 56203
	 

Les annonces plus spécifiques forment donc la moitié (en IPv4) des annonces dans la DFZ. Mais ce pourcentage augmente-t-il ou diminue-t-il ? Est-ce que la situation s'aggrave ? Huston utilise les données BGP accumulées depuis dix ans (si vous voulez faire pareil, les annonces BGP sont archivées et disponibles, entre autres à RouteViews). Eh bien, il n'y a pas de changement en IPv4 : le pourcentage de 50 % des annonces étant des annonces plus spécifiques qu'une annonce existante n'a pas changé depuis dix ans (mais la proportion des trois cas a changé : lisez l'article). En revanche, il est passé de 20 à 40 % en IPv6 dans le même temps, mais je ne suis pas sûr qu'on puisse en tirer des conclusions solides : il y a dix ans, IPv6 était peu présent dans la DFZ.

Ça, c'était le pourcentage des préfixes. Le nombre de préfixes à gérer a un effet négatif sur le routeur, car davantage de préfixes veut dire davantage de mémoire consommée par le routeur. La DFZ a actuellement plus de 700 000 préfixes (IPv4 et IPv6 mêlés). Une table de seulement 700 000 entrées ? Cela peut sembler peu à l'ère de la vidéo de chats en haute définition et du big data. Certes, un routeur du cœur de l'Internet n'a pas la même architecture qu'un PC de bureau, et la mémoire n'y est pas disponible dans les mêmes quantités mais, quand même, est-ce si grave ?

En fait, le plus gênant pour le routeur typique n'est pas la quantité de préfixes mais le rythme de changement (le churn). Davantage de préfixes veut surtout dire davantage de changements à traiter. Huston regarde donc dans la suite de son article l'effet des changements. Il constate que les préfixes plus spécifiques sont un peu plus « bruyants » (davantage de changements), au moins en IPv4. Les préfixes plus spécifiques du deuxième cas (ingénierie de trafic) sont les plus bruyants, ce qui est assez logique. Attention avec ces statistiques, toutefois : Huston rappelle que le rythme de changement varie énormément selon les préfixes. Certains changent tout le temps, d'autres sont très stables.

Maintenant, venons-en aux actions possibles. Est-ce que ces préfixes plus spécifiques, émis par des opérateurs égoïstes et payés par tous, doivent être activement combattus, pour en réduire le nombre ? C'est l'idée qui est derrière des actions comme le CIDR report. En analysant automatiquement les préfixes de la DFZ, et en publiant la liste des opérateurs qui abusent le plus, on espère leur faire honte (cette technique du name and shame est très courante sur Internet, puisqu'il n'existe pas d'État central qui puisse faire respecter des règles et que les opérateurs n'ont, heureusement, pas de police ou d'armée privée), et diminuer avec le temps le pourcentage de préfixes « inutiles ». Par exemple, Huston calcule que la suppression de tous les plus spécifiques du cas 3 (recouvrement complet) diminuerait la table de routage globale de 20 % et le rythme des changements de 10 à 15 %.

En conclusion, Huston estime qu'on ne peut pas parler de tous les préfixes moins spécifiques de la même façon, certains sont réellement utiles. Certes, d'autres ne le sont pas, mais les supprimer serait une tâche longue et difficile, pour un bénéfice limité. Huston résume en disant que les préfixes plus spécifiques sont un problème agaçant, mais pas grave.


L'article seul

Observations about the attack on WikiLeaks

First publication of this article on 1 September 2017


On 30 August, this year, a technical attack was performed against WikiLeaks, leading some visitors of WikiLeaks' Web site to see instead a claim by "OurMine" that they seized control of WikiLeaks' servers. A lot of stupid things, by ignorant people (both WikiLeaks fans and enemies), have been said on the networks, about this attack. Most of the time, they did not bother to check any facts, and they did not demonstrate any knowledge of the technical infrastructure. Here, I want to describe the bare facts, as seen from technical observations. Spoiler: I have no sensational revelations to make.

First, the basic fact: some people saw something which was obviously not WikiLeaks' Web site: screenshots of the page are here or here. Some people deduced from that that WikiLeaks' Web server was cracked and the crackers modified its content (you can find this in The Verge for instance). That was a bold deduction: the complete process from the user's browser to the display of the Web page is quite complicated, with a lot of actors, and many things can go wrong.

In the case of WikiLeaks, it appeared rapidly that the Web server was not cracked but that the attack targeted successfully the wikileaks.org domain name. Observations (more on that later) show that the name wikileaks.org was not resolved into the usual IP address but in another one, located in a different hoster. How is it possible? What are the consequences?

You should remember that investigation of digital incidents on the Internet is difficult. The external analyst does not have all the data. Sometimes, when the analysis starts, it is too late, the data already changed. And the internal analyst almost never publishes everything, and sometimes even lies. There are some organisations that are more open in their communication (see this Cloudflare report or this Gandi report) but they are the exceptions rather than the rule. Here, WikiLeaks reacted like the typical corporation, denying the problem, then trying to downplay it, and not publishing anything of value for the users. So, most of the claims that you can read about network incidents are not backed by facts, specially not publicly-verifiable facts. The problem is obviously worse in that case, because WikiLeaks is at the center of many hot controversies. For instance, some WikiLeaks fans claimed from the beginning "WikiLeaks' servers have not been compromised" while they had zero actual information, and, anyway, not enough time to analyze it.

So, the issue was with the domain name wikileaks.org. To explain what happened, we need to go back to the DNS, both a critical infrastructure of the Internet, and a widely unknown (or underknown) technology. The DNS is a database indexed by domain names (like wikileaks.org or ssi.gouv.fr). When you query the DNS for a given domain name, you get various technical informations such as IP addresses of servers, cryptographic keys, name of servers, etc. When the typical Web browser goes to http://www.okstate.com/, the software on the user's machine performs a DNS query for the name www.okstate.com, and gets back the IP address of the HTTP server. It then connects to the server.

From this very short description, you can see that s·he who controls the DNS controls where the user will eventually go and what s·he will see. And the entire DNS resolution process (from a name to the data) is itself quite complicated, offering many opportunities for an attacker. Summary so far: DNS is critical, and most organisations underestimate it (or, worse, claim it is not their responsability).

And where do the data in the DNS come from? That's the biggest source of vulnerabilities: unlike what many people said, most so-called "DNS attacks" are not DNS attacks at all, meaning they don't exploit a weakness in the DNS protocol. Most of the time, they are attacks against the provisioning infrastructure, the set of actors and servers that domain name holders (such as WikiLeaks for wikileaks.org) use to provision the data. Let's say you are Pat Smith, responsible for the online activity of an organisation named the Foobar Society. You have the domain name foobar.example. The Web site is hosted at Wonderful Hosting, Inc. After you've choosen a TLD (and I recommend you read the excellent EFF survey before you do so), you'll typically need to choose a registrar which will act as a proxy between you and the actual registry of the TLD (here, the fictitious .example). Most of the time, you, Pat Smith, will connect to the Web site of the registrar, create an account, and configure the data which will ultimately appear in the DNS. For instance, when the Web site is created at Wonderful Hosting, Pat will enter its IP address in the control panel provided by the registrar. You can see immediately that this required Pat to log in the said control panel. If Pat used a weak password, or wrote it down under h·is·er desk or if Pat is gullible and believes a phone call asking h·im·er to give the password, the account may be compromised, and the attacker may log in instead of Pat and put the IP address of h·er·is choosing. This kind of attacks is very common, and illustrate the fact that not all attacks are technically complicated.

So, what happened in the WikiLeaks case? (Warning, it will now become more technical.) We'll first use a "passive DNS" base, DNSDB. This sort of databases observes the DNS traffic (which is most of the time in clear, see RFC 7626) and record it, allowing its users to time-travel. DNSDB is not public, I'm sorry, so for this one, you'll have to trust me. (That's why real-time reaction is important: when you arrive too late, the only tools to observe an attack are specialized tools like this one.) What's in DNSDB?

      
;;  bailiwick: org.
;;      count: 9
;; first seen: 2017-08-30 04:28:40 -0000
;;  last seen: 2017-08-30 04:30:28 -0000
wikileaks.org. IN NS ns1.rivalhost.com.
wikileaks.org. IN NS ns2.rivalhost.com.
wikileaks.org. IN NS ns3.rivalhost.com.

;;  bailiwick: org.
;;      count: 474
;; first seen: 2017-08-30 04:20:15 -0000
;;  last seen: 2017-08-30 04:28:41 -0000
wikileaks.org. IN NS ns1.rival-dns.com.
wikileaks.org. IN NS ns2.rival-dns.com.
wikileaks.org. IN NS ns3.rival-dns.com.
    

What does it mean? That during the attack (around 04:30 UTC), the .org registry was replying with the illegitimate set of servers. The usual servers are (we use the dig tool, the best tool to debug DNS issues):


% dig @a0.org.afilias-nst.info. NS wikileaks.org
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21194
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 3
...
;; AUTHORITY SECTION:
wikileaks.org.		86400 IN NS ns1.wikileaks.org.
wikileaks.org.		86400 IN NS ns2.wikileaks.org.

;; ADDITIONAL SECTION:
ns1.wikileaks.org.	86400 IN A 46.28.206.81
ns2.wikileaks.org.	86400 IN A 46.28.206.82
...
;; SERVER: 2001:500:e::1#53(2001:500:e::1)
;; WHEN: Fri Sep 01 09:18:14 CEST 2017
...

    

(And, yes, there is a discrepancy between what is served by the registry and what's inside nsX.wikileaks.org name servers: whoever manages WikiLeaks DNS does a sloppy job. That's why it is often useful to query the parent's name servers, like I did here.)

So, the name servers were changed, for rogue ones. Note there was also a discrepancy during the attack. These rogue servers gave a different set of NS (Name Servers), according to DNSDB:

;;  bailiwick: wikileaks.org.
;;      count: 1
;; first seen: 2017-08-31 02:02:38 -0000
;;  last seen: 2017-08-31 02:02:38 -0000
wikileaks.org. IN NS ns1.rivalhost-global-dns.com.
wikileaks.org. IN NS ns2.rivalhost-global-dns.com.     

Note that it does not mean that the DNS hoster of the attacker, Rival, is an accomplice. They may simply have a rogue customer. Any big service provider will have some rotten apples among its clients.

You can see the date of the last change in whois output, when everything was put back in place:

% whois wikileaks.org
...
Updated Date: 2017-08-31T15:01:04Z
    

Surely enough, the rogue name servers were serving IP addresses pointing to the "false" Web site. Again, in DNSDB:

    
;;  bailiwick: wikileaks.org.
;;      count: 44
;; first seen: 2017-08-30 04:29:07 -0000
;;  last seen: 2017-08-31 07:22:05 -0000
wikileaks.org. IN A 181.215.237.148

The normal IP addresses of WikiLeaks are in the prefixes 95.211.113.XXX, 141.105.XXX and 195.35.109.XXX (dig A wikileaks.org if you want to see them, or use a DNS Looking Glass). 181.215.237.148 is the address of the rogue Web site, hosted by Rival again, as can be seen with the whois tool:

% whois 181.215.237.148
inetnum:     181.215.236/23
status:      reallocated
owner:       Digital Energy Technologies Chile SpA
ownerid:     US-DETC5-LACNIC
responsible: RivalHost, LLC.
address:     Waterwood Parkway, 1015, Suite G, C-1
address:     73034 - Edmond - OK
country:     US
owner-c:     VIG28
tech-c:      VIG28
abuse-c:     VIG28
...
nic-hdl:     VIG28
person:      AS61440 Network Operating Center
e-mail:      noc@AS61440.NET
address:     Moneda, 970, Piso 5
address:     8320313 - Santiago - RM
country:     CL
  

(It also shows that this prefix was allocated in Chile, the world is a complicated place, and the Internet even more so.)

So, this was the modus operandi of the cracker. S·he managed to change the set of name servers serving wikileaks.org, and that gave h·im·er the ability to send visitors to a server s·he controlled. (Note that this HTTP server, 181.215.237.148, no longer serves the cracker's page: it was probably removed by the provider.)

Many people on the social networks claimed that the attack was done by "DNS poisoning". First, a word of warning by a DNS professional: when someone types "DNS poisoning", you can be pretty sure s·he knows next to nothing about DNS. DNS poisoning is a very specific attack, for which we have solutions (DNSSEC, mentioned later), but it does not seem to be very common (read again my warning at the beginning: most attacks are never properly analyzed and documented, so it is hard to be more precise). What is very common are attacks against the domain name provisioning system. This is, for instance, what happened to the New York Times in 2013, from an attack by the infamous SEA (see NYT paper and a technical analysis). More recently, there was the attack against St. Louis Federal Reserve and many others. These attacks don't use the DNS protocol and it is quite a stretch to label them as "DNS attacks" or, even worse, "DNS poisoning".

What are the consequences of such an attack? As explained earlier, once you control the DNS, you control everything. You can redirect users to a Web site (not only the external visitors, but also the employees of the targeted organisation, when they connect to internal services, potentially stealing passwords and other informations), hijack the emails, etc. So, claiming that "the servers were not compromised" (technically true) is almost useless. With an attack on domain names, the cracker does not need to compromise the servers.

Who was cracked in the WikiLeaks case? From the outside, we can say with confidence that the name servers were changed. The weakness could have been at the holder (WikiLeaks), at the registrar (Dynadot, an information you also get with whois), or at the registry (.org, administratively managed by PIR and technically by Afilias). From the information available, one cannot say where the problem was (so, people who publicly shouted that "WikiLeaks is not responsible" were showing their blind faith, not their analytic abilities). Of course, most of the times, the weakest link is the user (weak password to the registrar portal, and not activating 2FA), but some registrars or registries displayed in the past serious security weaknesses. The only thing we can say is that no other domain name appeared to have been hijacked. (When someone takes control of a registrar or registry, s·he can change many domain names.)

I said before that, when you control a domain name, you can send both external and internal visitors to the server you want. That was not entirely true, since good security relies on defence in depth and some measures can be taken to limit the risk, even if your domain name is compromised. One of them is of course having HTTPS (it is the case of WikiLeaks), with redirection from the plain HTTP site, and HSTS (standardized in RFC 6797), to avoid that regular visitors go through the insecure HTTP. Again, WikiLeaks use it:

%  wget --server-response --output-document /dev/null https://wikileaks.org/
...
Strict-Transport-Security: max-age=25920000; includeSubDomains; preload
   

These techniques will at least raise an alarm, telling the visitor that something is fishy. (There is also HPKPRFC 7649 - but it does not seem deployed by Wikileaks; it should be noticed it is more risky.)

In the same way, using Tor to go to a .onion URL would also help. But I have not been able to find a .onion for WikiLeaks (the http://suw74isz7wqzpmgu.onion/ indicated on the wiki does not work, the http://wlupld3ptjvsgwqw.onion seems to be just for uploading).

One can also limit the risk coming from an account compromise by enabling registry lock, a technique offered by most TLD (including .org) to prevent unauthorized changes. When activated, it requires extra steps and checking for any change. I cannot say, from the outside, if WikiLeaks enabled it but sensitive domain names must do it.

Funny enough, with so many people claiming it was "DNS poisoning", the best protection against this specific attack, DNSSEC, is not enabled by WikiLeaks (there is a DNSSEC key in wikileaks.org but no signatures and no DS record in the parent). If wikileaks.org was signed, and if you use a validating DNS resolver (everybody should), you cannot fall for a DNS poisoning attack against WikiLeaks. Of course, if the attack is, instead, a compromise of holder account, registrar or registry, DNSSEC would not help a lot.

A bit of technical fun at the end. WikiLeaks uses glue records for its name servers. They are nameserver names which are under the domain they serve, thus creating a chicken-and-egg problem. To allow the DNS client to query them, the parent has to know the IP address of this name server. This is what is called a glue record. DNSDB shows us that the glue for ns1.wikileaks.org was apparently modified (note that it was several hours after the main attack):

;;  bailiwick: org.
;;      count: 546
;; first seen: 2017-08-31 00:23:13 -0000
;;  last seen: 2017-08-31 06:22:42 -0000
ns1.wikileaks.org. IN A 191.101.26.67
    

This machine is still up and serves a funny value for wikileaks.org (again, you can use a DNS Looking Glass):


% dig @191.101.26.67 A wikileaks.org
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53887
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
...
;; ANSWER SECTION:
wikileaks.org.		400 IN A 127.0.0.1

    

This IP address, meaning localhost, was indeed seen by some DNSDB sensors:

;;  bailiwick: wikileaks.org.
;;      count: 1
;; first seen: 2017-08-31 09:17:29 -0000
;;  last seen: 2017-08-31 09:17:29 -0000
wikileaks.org. IN A 127.0.0.1	
      

Since the DNS heavily relies on caching, the information was still seen even after the configuration was fixed. Here, we use the RIPE Atlas probes with the atlas-resolve tool to see how many probes still saw the wrong value (pay attention to the date and time, all in UTC, which is the rule when analyzing Internet problems):

% atlas-resolve -r 1000 -t A wikileaks.org
[141.105.65.113 141.105.69.239 195.35.109.44 195.35.109.53 95.211.113.131 95.211.113.154] : 850 occurrences 
[195.175.254.2] : 2 occurrences 
[127.0.0.1] : 126 occurrences 
Test #9261634 done at 2017-08-31T10:03:24Z
    

L'article seul

RFC 8216: HTTP Live Streaming

Date de publication du RFC : Août 2017
Auteur(s) du RFC : R. Pantos (Apple), W. May (Major League Baseball Advanced Media)
Pour information
Première rédaction de cet article le 1 septembre 2017


Ce RFC décrit la version 7 du protocole HTTP Live Streaming, qui permet de distribuer des flux multimédia, typiquement de la vidéo.

Utiliser HTTP, c'est bien, ça passe quasiment tous les pare-feux, et ça permet de réutiliser l'infrastructure existante, comme les serveurs, et les caches. (Au contraire, un protocole comme RTP, RFC 3550, a souvent du mal à passer les différentes middleboxes.) La manière triviale de distribuer une vidéo avec HTTP est d'envoyer un fichier vidéo au bon format, avec des méthodes HTTP standard, typiquement en réponse à un GET. Mais cette méthode a des tas de limites. Par exemple, elle ne permet pas de s'adapter à une capacité variable du réseau. Par contre, le protocole décrit dans ce RFC, quoique plus compliqué qu'un simple GET, permet cet ajustement (et plein d'autres).

HTTP Live Streaming (ou simplement Live Streaming, ou encore HLS) existe depuis longtemps, la première spécification a été publiée (par Apple) en 2009, mais c'est la première fois qu'il se retrouve dans un RFC, ce qui devrait permettre d'améliorer encore l'interopérabilité.

Donc, brève description de HTTP Live Streaming : une ressource multimédia qu'on veut diffuser est représentée par un URI. Ceui-ci désigne une playlist, qui est un fichier texte contenant les URI des données multimedia et diverses informations. Les données multimédia figurent dans plusieurs segments ; en jouant tous les segments successivement, on obtient la vidéo souhaitée. Le client n'a pas ainsi à tout charger, il peut récupérer les segments au fur et à mesure de ses besoins. (Au lieu de « segments », mmu_man suggère de parler d'un « émincé de fichiers ».) Voici l'exemple de playlist que donne le RFC :

#EXTM3U
#EXT-X-TARGETDURATION:10

#EXTINF:9.009,
http://media.example.com/first.ts
#EXTINF:9.009,
http://media.example.com/second.ts
#EXTINF:3.003,
http://media.example.com/third.ts
    

Ce format de playlist est dérivé du format M3U et est généralement nommé « M3U étendu ». On y trouve successivement l'identificateur du format (si ça commence par #EXTM3U, c'est bien une playlist de notre RFC), la durée maximale d'un segment en secondes, puis les trois segments, avec la durée de chacun. Cette durée, indiquée par l'étiquette #EXTINF, est un des rares éléments obligatoires dans une playlist. Il y a bien sûr plein d'autres choses qu'on peut mettre dans un fichier playlist, la section 4 du RFC en donne la liste complète. Ainsi, #EXT-X-BYTERANGE permettra d'indiquer qu'on ne veut jouer qu'une partie d'une vidéo. Des exemples plus complets sont donnés dans la section 8 du RFC.

Ça, c'était la media playlist, qui se limite à lister des ressources multimédias. La master playlist, dont le format est le même, est plus riche, et permet de spécifier des versions alternatives d'un même contenu. Ces variantes peuvent être purement techniques (« variant stream », deux encodages d'une même vidéo avec différents formats) ou porter sur le fond : par exemple deux pistes audio dans des langues différentes, ou deux vidéos d'un même évènement filmé depuis deux points de vue distincts. Les variantes techniques servent au client à s'ajuster aux conditions réelles du réseau, les variantes de fond (nommées renditions) servent au client à donner à l'utilisateur ce qu'il préfère. Ici, par exemple, on a dans cette master playlist trois variantes techniques (trois résolutions, de la plus basse, à la plus élevée, qui va nécessiter une bonne capacité réseau), et trois « renditions », correspondant à trois points de vue (centerfield et dugout sont des termes de baseball, ce qui est logique vu l'employeur d'un des deux auteurs du RFC) :

#EXTM3U
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Main", \
   DEFAULT=YES,URI="low/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Centerfield", \
   DEFAULT=NO,URI="low/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Dugout", \
   DEFAULT=NO,URI="low/dugout/audio-video.m3u8"

#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS="...",VIDEO="low"
   low/main/audio-video.m3u8

#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Main", \
   DEFAULT=YES,URI="mid/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Centerfield", \
   DEFAULT=NO,URI="mid/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Dugout", \
   DEFAULT=NO,URI="mid/dugout/audio-video.m3u8"

#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS="...",VIDEO="mid"
   mid/main/audio-video.m3u8

#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Main", \
   DEFAULT=YES,URI="hi/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Centerfield", \
   DEFAULT=NO,URI="hi/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Dugout", \
   DEFAULT=NO,URI="hi/dugout/audio-video.m3u8"

#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS="...",VIDEO="hi"
   hi/main/audio-video.m3u8
    

Si vous voulez le voir en vrai, et que vous êtes abonné à Free, la Freebox distribue sa playlist sous ce format (mais avec le mauvais type MIME) :


% curl -s http://mafreebox.freebox.fr/freeboxtv/playlist.m3u
#EXTM3U
#EXTINF:0,2 - France 2 (bas débit)
rtsp://mafreebox.freebox.fr/fbxtv_pub/stream?namespace=1&service=201&flavour=ld
#EXTINF:0,2 - France 2 (HD)
rtsp://mafreebox.freebox.fr/fbxtv_pub/stream?namespace=1&service=201&flavour=hd
#EXTINF:0,2 - France 2 (standard)
rtsp://mafreebox.freebox.fr/fbxtv_pub/stream?namespace=1&service=201&flavour=sd
#EXTINF:0,2 - France 2 (auto)
rtsp://mafreebox.freebox.fr/fbxtv_pub/stream?namespace=1&service=201
#EXTINF:0,3 - France 3 (standard)
rtsp://mafreebox.freebox.fr/fbxtv_pub/stream?namespace=1&service=202&flavour=sd
...

    

(On me dit qu'avec certaines Freebox, il faut ajouter -L à curl, en raison d'une redirection HTTP.) Un client comme vlc peut prendre comme argument l'URI de la playlist, la télécharger et jouer ensuite les vidéos.

(Tous les moyens sont bons pour télécharger la playlist, le RFC n'impose pas un mécanisme particulier.) Télécharger chacun des segments se fait a priori avec HTTP, RFC 7230, mais vous voyez plus haut que, sur la Freebox, c'est RTSP, RFC 7826. Le serveur, lui, est un serveur HTTP ordinaire. L'auteur a « juste » eu à découper sa vidéo en segments (ou la garder en un seul morceau si elle est assez courte), à fabriquer un fichier playlist, puis à le servir avec le type MIME application/vnd.apple.mpegurl. (La section 6 du RFC décrit plus en détail ce que doivent faire client et serveur.)

Il est recommandé que chaque segment soit auto-suffisant, c'est-à-dire puisse être décodé sans avoir besoin des autres segments. Ainsi, si les segments sont en H.264, chacun doit avoir un Instantaneous Decoding Refresh (IDR).

Plusieurs formats sont évidemment possibles dans une playlist HTTP Live Streaming, MPEG-2, MPEG-4 fragmenté, WebVTT pour avoir les sous-titres, et un format pour l'audio (qui accepte entre autres MP3).

HTTP Live Streaming permet aussi de servir du direct. Prenons par exemple le flux vidéo de l'Assemblée Nationale, qui permet de suivre les débats parlementaires :

    
% curl -s http://videos-diffusion-live.assemblee-nationale.fr/live/live1/stream1.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:382
#EXTINF:9.996,
media_1_4805021_382.ts
#EXTINF:11.483,
media_1_4805021_383.ts
...

Un peu plus tard, les fichiers ont changé, le client HLS doit recharger la playlist régulièrement (section 6.2.2 du RFC), et charger de nouveaux fichiers vidéo :

% curl -s http://videos-diffusion-live.assemblee-nationale.fr/live/live1/stream1.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:390
#EXTINF:10.039,
media_1_4805021_390.ts
#EXTINF:11.242,
media_1_4805021_391.ts
...

(Notez également l'incrémentation du #EXT-X-MEDIA-SEQUENCE.) Ainsi, l'utilisateur a l'impression d'une vidéo continue, alors que le logiciel client passe son temps à charger des segments différents et à les afficher à la suite.

Notez que HTTP Live Streaming n'est pas le seul mécanisme dans ce domaine, son principal concurrent est MPEG-DASH.

Si vous voulez lire davantage :


Téléchargez le RFC 8216


L'article seul

RFC 6844: DNS Certification Authority Authorization (CAA) Resource Record

Date de publication du RFC : Janvier 2013
Auteur(s) du RFC : P. Hallam-Baker (Comodo), R. Stradling (Comodo)
Chemin des normes
Première rédaction de cet article le 31 août 2017


Ce RFC a quelques années mais n'avait connu que peu de déploiement effectif. Cela va peut-être changer à partir du 8 septembre 2017, où les AC ont promis de commencer à vérifier les enregistrements CAA (Certification Authority Authorization) publiés dans le DNS. Ça sert à quoi ? En gros, un enregistrement CAA publié indique quelles AC sont autorisées à émettre des certificats pour ce domaine. Le but est de corriger très partiellement une des plus grosses faiblesses de X.509, le fait que n'importe quelle AC peut émettre des certificats pour n'importe quel domaine, même si ce n'est pas un de ses clients.

C'est donc une technique très différente de DANE (RFC 6698), les seuls points communs étant l'utilisation du DNS pour sécuriser les certificats. DANE est déployé chez le client TLS, pour qu'il vérifie le certificat utilisé, CAA est surtout dans l'AC, pour limiter le risque d'émission d'un certificat malveillant (par exemple, CAA aurait peut-être empêché le faux certificat Gmail du ministère des finances.) Disons que CAA est un contrôle supplémentaire, parmi ceux que l'AC doit (devrait) faire. Les clients TLS ne sont pas censés le tester (ne serait-ce que parce que l'enregistrement CAA a pu changer depuis l'émission du certificat, la durée de vie de ceux-ci étant en général de plusieurs mois). CAA peut aussi servir à des auditeurs qui veulent vérifier les pratiques d'une AC (même avertissement : le certificat a pu être émis alors que l'enregistrement CAA était différent.)

La section 3 de notre RFC présente l'enregistrement CAA (la syntaxe détaillée est en section 5). Il a été ajouté au registre des types d'enregistrements sous le numéro 257. Il comprend une série d'options (flags) et une propriété qui est sous la forme {clé, valeur}. Un nom peut avoir plusieurs propriétés. Pour l'instant, une seule option est définie (un registre existe pour les options futures), « issuer critical » qui indique que cette propriété est cruciale : si on ne la comprend pas, le test doit être considéré comme ayant échoué et l'AC ne doit pas produire de certificat.

Les propriétés possibles sont, à l'heure actuelle (d'autres pourront être ajoutée dans le registre IANA) :

  • issue, la principale, qui indique une AC autorisée à émettre des certificats pour ce domaine (l'AC est indiquée par son nom de domaine),
  • issuewild, idem, mais avec en plus la possibilité pour l'AC d'émettre des certificats incluants des jokers,
  • iodef, qui indique où l'AC doit envoyer d'éventuels rapports d'échec, pour que le titulaire du nom de domaine puisse les corriger. Un URL est indiqué pour cela, et le rapport doit être au format IODEF (RFC 7970).

Voici par exemple l'enregistrement CAA de mon domaine personnel :


% dig CAA bortzmeyer.org
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61450
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 7, ADDITIONAL: 7
...
;; ANSWER SECTION:
bortzmeyer.org.		26786 IN CAA 0 issue "cacert.org"
bortzmeyer.org.		26786 IN CAA 0 issuewild "\;"
...

  

Il indique que seule l'AC CAcert peut créer un certificat pour ce domaine (et sans les jokers). Bon, c'est un peu inutile car CAcert ne teste pas les enregistrements CAA, mais c'est juste pour jouer. Je n'ai pas mis d'iodef mais il aurait pu être :

bortzmeyer.org. CAA 0 iodef "mailto:security@bortzmeyer.org"
  

Et, dans ce cas, l'AC peut écrire à security@bortzmeyer.org, avec le rapport IODEF en pièce jointe.

Des paramètres peuvent être ajoutés, le RFC cite l'exemple d'un numéro de client :

example.com.   CAA 0 issue "ca.example.net; account=230123" 
  

Une fois les enregistrements CAA publiés, comment sont-ils utilisés (section 4) ? L'AC est censée interroger le DNS pour voir s'il y a un CAA (on note que DNSSEC est recommandé, mais n'est pas obligatoire, ce qui réduit le service déjà faible qu'offre CAA). S'il n'y en a pas, l'AC continue avec ses procédures habituelles. S'il y a un CAA, deux cas : il indique que cette AC peut émettre un certificat pour le domaine, et dans ce cas-là, c'est bon, on continue avec les procédures habituelles. Second cas, le CAA ne nomme pas cette AC et elle doit donc renoncer à faire un certificat sauf s'il y a une exception configurée pour ce domaine (c'est la deuxième faille de CAA : une AC peut facilement passer outre et donc continuer émettre de « vrais/faux certificats »).

Notez que le RFC ne semble pas évoquer la possibilité d'imposer la présence d'un enregistrement CAA. C'est logique, vu le peu de déploiement de cette technique mais cela veut dire que « qui ne dit mot consent ». Pour la plupart des domaines, la vérification du CAA par l'AC ne changera rien.

Notez que, si aucun enregistrement CAA n'est trouvé, l'AC est censé remonter l'arbre du DNS. (C'est pour cela que SSL [sic] Labs trouve un enregistrement CAA pour mercredifiction.bortzmeyer.org : il a utilisé le CAA du domaine parent, bortzmeyer.org.) Si example.com n'a pas de CAA, l'AC va tester .com, demandant ainsi à Verisign si son client peut avoir un certificat et de qui. Cette erreur consistant à grimper sur l'arbre avait déjà été dénoncée dans le RFC 1535, mais apparemment la leçon n'a pas été retenue.

Enfin, la section 6 du RFC analyse les différents problèmes de sécurité que peut poser CAA :

  • Le respect de l'enregistrement CAA dépend de la bonne volonté de l'AC (et CAA ne remplace donc pas DANE),
  • Sans DNSSEC, le test CAA est vulnérable à des attaques par exemple par empoisonnement (le RFC conseille de ne pas passer par un résolveur normal mais d'aller demander directement aux serveurs faisant autorité, ce qui ne résoud pas le problème : le programme de test peut se faire empoisonner, lui aussi, d'autant plus qu'il prend sans doute moins de précautions qu'un résolveur sérieux),
  • Et quelques autres risques plus exotiques.

Comme indiqué au début, le CA/Browser Forum a décidé que le test des CAA serait obligatoire à partir du 8 septembre 2017. (Cf. la décision.) Parmi les rares enregistrements CAA dans la nature, on trouve celui de Google, qui autorise deux AC :


% dig CAA google.com
...
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55244
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
...
;; ANSWER SECTION:
google.com.		86400 IN CAA 0 issue "pki.goog"
google.com.		86400 IN CAA 0 issue "symantec.com"
...

  

(Le TLD .goog est apparemment utilisé par Google pour son infrastructure, .google étant plutôt pour les choses publiques.) Notez que gmail.com, lui, pourtant souvent détourné par des gouvernements et des entreprises qui veulent surveiller le trafic, n'a pas d'enregistrement CAA. Le célèbre SSL [sic] Labs teste la présence d'un enregistrement CAA. S'il affiche « DNS Certification Authority Authorization (CAA) Policy found for this domain », c'est bon. Regardez le cas de Google.

Le type CAA est relativement récent (quatre ans quand même), donc il crée des problèmes amusants avec les serveurs DNS bogués comme ceux de BNP Paribas, qui timeoutent lorsque le type demandé n'est pas A :


% dig CAA mabanqueprivee.bnpparibas

; <<>> DiG 9.10.3-P4-Debian <<>> CAA mabanqueprivee.bnpparibas
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 42545
                                       ^^^^^^^^

  

Quelques lectures et ressources pour finir :


Téléchargez le RFC 6844


L'article seul

RFC 8228: Guidance on Designing Label Generation Rulesets (LGRs) Supporting Variant Labels

Date de publication du RFC : Août 2017
Auteur(s) du RFC : A. Freytag
Pour information
Première rédaction de cet article le 23 août 2017


Le RFC 7940 définissait un langage, LGR (Label Generation Ruleset), pour exprimer des politiques d'enregistrement de noms écrits en Unicode, avec les éventuelles variantes. Par exemple, avec LGR, on peut exprimer une politique disant que les caractères de l'alphabet latin et eux seuls sont autorisés, et que « é » est considéré équivalent à « e », « ç » à « c », etc. Ce nouveau RFC donne quelques conseils aux auteurs LGR. (Personnellement, je n'ai pas trouvé qu'il apportait grand'chose par rapport au RFC 7940.)

LGR peut s'appliquer à tous les cas où on a des identificateurs en Unicode mais il a surtout été développé pour les IDN (RFC 5890). Les exemples de ce RFC 8228 sont donc surtout pris dans ce domaine d'application. Un ensemble de règles exprimées dans le langage du RFC 7940 va définir deux choses :

  • Si un nom donné est acceptable ou pas (dans l'exemple donné plus haut, un registre de noms de domaine ouest-européen peut décider de n'autoriser que l'alphabet latin),
  • Et quels noms sont des variantes les uns des autres. Une variante est un nom composé de caractères Unicode différents mais qui est considéré comme « le même » (la définition de ce que c'est que « le même » n'étant pas technique ; par exemple, un anglophone peut considérer que theatre et theater sont « le même » mot, alors que, du point de vue technique, ce sont deux chaînes de caractère différentes). Le RFC dit que deux noms sont « le même » s'ils sont visuellement similaires (google vs. goog1e), mais c'est une notion bien trop floue, et qui dépend en outre de la police de caractères utilisée (avec Courier, google et goog1e sont difficilement distinguables). Le RFC ajoute qu'il y a également le cas de la similarité sémantique (le cas de theatre vs. theater, ou bien celui des sinogrammes traditionnels vs. les simplifiés ou encore, mais c'est compliqué, le cas des accents en français, avec « café » et « cafe » mais aussi avec « interne » et « interné »).

L'idée est que le registre utilise ensuite ces règles pour accepter ou refuser un nom et, si le nom est accepté, pour définir quelles variantes deviennent ainsi bloquées, ou bien attribuées automatiquement au même titulaire. Notez une nouvelle fois qu'il s'agit d'une décision politique et non technique. Le RFC dit d'ailleurs prudemment dans la section 1 qu'il ne sait pas si ce concept de variante est vraiment utile à quoi que ce soit. (J'ajoute qu'en général ce soi-disant risque de confusion a surtout été utilisé comme FUD anti-Unicode, par des gens qui n'ont jamais digéré la variété des écritures humaines.)

J'ai dit qu'une variante était « le même » que le nom de base. Du point de vue mathématique, la relation « est une variante de » doit donc être symétrique et transitive (section 2). « Est une variante de » est noté avec un tiret donc A - B signifie « A est une variante de B » (et réciproquement, vu la symétrie). Notez que le RFC 7940 utilisait un langage fondé sur XML alors que ce RFC 8228 préfère une notation plus compacte. La section 18 de notre RFC indique la correspondance entre la notation du RFC 7940 et celle du nouveau RFC.

Mais toutes les relations entre noms « proches » ne sont pas forcément symétriques et transitives. Ainsi, la relation « ressemble à » (le chiffre 1 ressemble à la lettre l minuscule) est non seulement vague (cela dépend en effet de la police) mais également non transitive (deux noms ressemblent chacun à un nom dont la forme est quelque part entre eux, sans pour autant se ressembler entre eux).

Le RFC 7940 permet de définir des relations qui ne sont ni symétriques, ni transitives. Mais ce nouveau RFC 8228 préfère les relations d'équivalence.

Bon, et maintenant, comment on crée les variantes d'un nom ou d'un composant d'un nom (section 6) ? On remplace chaque caractère du nom originel par toutes ses variantes possibles (le fait qu'un caractère correspond à un autre est noté -->). Ainsi, si on a décidé que « e » et « é » étaient équivalents (é --> e), le nom « interne » aura comme variantes possibles « interné », « intérne » et « intérné » (oui, une seule de ces variantes a un sens en français, je sais). Une fois cette génération de variantes faites, le registre, selon sa politique, pourra l'utiliser pour, par exemple, refuser l'enregistrement d'« interné » si « interne » est déjà pris (je le répète parce que c'est important : ce RFC ne décrit pas de politique, l'exemple ci-dessus n'est qu'un exemple).

En pratique, travailler caractère par caractère n'est pas toujours réaliste. Il y a des cas où c'est un groupe de caractères qui poserait problème. La section 7 introduit une amélioration où on peut étiqueter les correspondances de manière asymétrique. Le caractère x veut dire que l'allocation du nom est interdite (détails en section 9), le a qu'elle est autorisée (détails en section 8). On pourrait donc avoir « e x--> é » et « é a--> e » ce qui veut dire que la réservation de « interne » bloquerait celle d'« interné » mais pas le contraire.

Le monde des identificateurs étant très riche et complexe, on ne s'étonnera pas que des règles trop simples soient souvent prises en défaut. Ainsi, la variante peut dépendre du contexte : dans certaines écritures (comme l'arabe), la forme d'un caractère dépend de sa position dans le mot, et un même caractère peut donc entrainer de la confusion s'il est à la fin d'un mot mais pas s'il est au début. Il faut donc étiqueter les relations avec ce détail (par exemple, « final: C --> D » si le caractère noté C peut se confondre avec le caractère D mais uniquement si C est en fin de mot, cf. section 15 du RFC).

Si vous développez du LGR, au moins deux logiciels peuvent aider, celui de Viagénie, lgr-crore et lgr-django et un logiciel de l'auteur de ce RFC, développé pour le « ICANN Integration Panel work » mais qui ne semble pas publié.


Téléchargez le RFC 8228


L'article seul

RFC 8193: Information Model for Large-Scale Measurement Platforms (LMAPs)

Date de publication du RFC : Août 2017
Auteur(s) du RFC : T. Burbridge, P. Eardley (BT), M. Bagnulo (Universidad Carlos III de Madrid), J. Schoenwaelder (Jacobs University Bremen)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF lmap
Première rédaction de cet article le 22 août 2017


Ce RFC est l'un de ceux du groupe de travail IETF nommé LMAP, pour « Large-Scale Measurement Platforms ». Ce groupe développe des normes pour les systèmes de mesure à grande échelle, ceux où des centaines ou des milliers de sondes quantifient l'Internet. Il décrit le modèle de données utilisé.

Le projet LMAP est décrit dans les RFC 7536 (qui explique à quoi sert LMAP, avec deux exemples concrets) et RFC 7594 (qui normalise le vocabulaire et le cadre général). Pour résumer très vite, LMAP prévoit un Measurement Agent (la sonde) qui parle à un contrôleur, qui lui dit quoi faire, et à un collecteur, qui ramasse les données. Ce nouveau RFC décrit le modèle de données abstrait utilisé dans ces deux communications.

Rappelons que, dans le cadre défini pour LMAP, chaque MA (Measurement Agent) parle à un et un seul contrôleur, qui lui dit ce qu'il doit mesurer. (LMAP sépare le rôle du contrôleur et du collecteur, rôles que beaucoup de systèmes déployés fusionnent.) Il y a donc trois protocoles en jeu, le protocole de contrôle, entre le contrôleur et les MA, le protocole de collecte, entre les collecteurs et les MA, et enfin les protocoles utilisés dans les tests (ICMP ECHO, par exemple, mais aussi HTTP, DNS…) Enfin, il y a aussi la configuration nécessaire dans tous ces équipements. Tous ces points peuvent faire l'objet d'un travail de normalisation, mais ce RFC 8193 présente uniquement un modèle de données adapté au protocole de collecte et au protocole de contrôle.

La notion de modèle de données est expliquée dans le RFC 3444. En gros, il s'agit d'un modèle abstrait (très abstrait) décrivant quel genre de données sont échangées. Il peut se formaliser de manière un peu plus concrète par la suite, par exemple en utilisant le langage YANG (c'est justement ce que fait le RFC d'accompagnement, le RFC 8194). Un tel modèle sert à guider la normalisation, à faciliter la traduction d'un protocole dans un autre s'ils utilisent le même modèle de données, et à définir les données que doit mesurer et conserver le MA.

Le modèle défini ici utilise une notation formelle, utilisant des types de données classiques en programmation (section 3 du RFC) : int (un entier), boolean (une valeur logique), string, mais aussi des types plus riches comme datetime ou uri.

Le modèle lui-même est dans la section 4 du RFC. Je rappelle qu'il suit le cadre du RFC 7594. Il a six parties :

  • Informations de pré-configuration du MA (celle qui est faite en usine),
  • Informations de configuration du MA,
  • Instructions reçues par le MA (« envoie telle requête DNS à tel serveur, de telle heure à telle heure »),
  • Journal du MA, avec des évènements comme le redémarrage de la sonde,
  • État général du MA (« je sais faire des tests IPv6 »),
  • Et bien sûr, le résultat des mesures, ce qui est l'information la plus importante.

Le MA peut également avoir d'autres informations, par exemple des détails sur son type de connectivité.

Pour donner des exemples concrets, je vais souvent citer les sondes RIPE Atlas, même si ce système, conçu avant le RFC, ne suis pas exactement ce modèle. C'est sans doute l'un des plus grands, voire le plus grand réseau de points de mesure dans le monde, avec près de 10 000 sondes connectées. Par exemple, pour la configuration, les informations de pré-configuration d'une sonde Atlas comprennent une adresse MAC et un identificateur, qu'on peut voir sur la sonde : atlas-mac-id.jpg

Les sondes sont également pré-configurées avec le nom du contrôleur (qui sert également de collecteur). Elles acquièrent le reste de leur configuration par DHCP et SLAAC. Regardons par exemple la sonde d'identificateur #21660, installée à Cochabamba, on voit les adresses IP obtenues : sonde-atlas-21660.png

Les sondes Atlas disposent également d'étiquettes (tags) décrivant plus précisement certaines de leurs caractéristiques. Certaines des étiquettes sont attribuées manuellement par leur propriétaire (en bleu sur l'image ci-dessous), d'autres, qui peuvent être déterminées automatiquement, sont mises par le système (en vert sur l'image), et rentrent donc dans le cadre de la partie « état général de la sonde » du modèle : sonde-atlas-21660-tags.png

Les classes utilisées dans le modèle sont, entre autres :

  • Les agendas (schedules) qui indiquent quelle tâche doit être accomplie,
  • Les canaux de communication (channels),
  • les configurations (task configurations),
  • Les évènements (events), qui détaillent le moment où une tâche doit être exécutée.

Première partie, la pré-configuration (section 4.1). On y trouve les informations dont le MA aura besoin pour accomplir sa mission. Au minimum, il y aura un identificateur (par exemple l'adresse MAC), les coordonnées de son contrôleur (par exemple un nom de domaine, mais le RFC ne cite que le cas où ces coordonnées sont un URL, pour les protocoles de contrôle de type REST), peut-être des moyens d'authentification (comme le certificat de l'AC qui signera le certificat du contrôleur). URL et moyens d'authentification, ensemble, forment un canal de communication.

Dans le langage formel de ce modèle, cela fait :


     object {
         [uuid                ma-preconfig-agent-id;]
          ma-task-obj         ma-preconfig-control-tasks<1..*>;
          ma-channel-obj      ma-preconfig-control-channels<1..*>;
          ma-schedule-obj     ma-preconfig-control-schedules<1..*>;
         [uri                 ma-preconfig-device-id;]
          credentials         ma-preconfig-credentials;
     } ma-preconfig-obj;     

    

Cela se lit ainsi : l'information de pré-configuration (type ma-preconfig-obj) comprend divers attributs dont un identificateur (qui est un UUID, on a vu que les Atlas utilisaient un autre type d'identificateurs), des canaux de communication avec le contrôleur, et des lettres de créance à présenter pour s'authentifier. Notez que l'identificateur est optionnel (entre crochets).

Vous avez compris le principe ? On peut passer à la suite plus rapidement (je ne vais pas répéter les déclarations formelles, voyez le RFC). La configuration (section 4.2) stocke les informations volatiles (la pré-configuration étant consacrée aux informations plus stables, typiquement stockées sur une mémoire permanente).

Les instructions (section 4.3) sont les tâches que va accomplir la sonde, ainsi que l'agenda d'exécution, et bien sûr les canaux utilisé pour transmettre les résultats. La journalisation (section 4.4) permet d'enregistrer les problèmes (« mon résolveur DNS ne marche plus ») ou les évènements (« le courant a été coupé »).

Il ne sert évidemment à rien de faire des mesures si on ne transmet pas le résultat. D'où la section 4.6, sur la transmission des résultats de mesure au collecteur. La communication se fait via les canaux décrits en section 4.8.

Une dernière photo, pour clore cet article, une sonde RIPE Atlas en fonctionnement, et une autre sonde, la SamKnows : atlas-et-samknows.jpg

Merci à André Sintzoff pour avoir trouvé de grosses fautes.


Téléchargez le RFC 8193


L'article seul

jq, traiter du JSON en ligne de commande

Première rédaction de cet article le 9 août 2017


Aujourd'hui, énormément de données sont distribuées dans le format JSON. La simplicité de ce format, normalisé dans le RFC 7159, et bien sûr son utilisation dans JavaScript expliquent ce succès. Pour le transport de données (celui du texte est une autre histoire), JSON a largement remplacé XML. Il existe donc des bibliothèques pour traiter le JSON dans tous les langages de programmation. Mais si on veut une solution plus simple, permettant de traiter du JSON depuis la ligne de commande ? C'est là qu'intervient jq, qui permet à la fois de faire des opérations simples en ligne de commande sur des données JSON, et de programmer des opérations complexes, si nécessaires.

jq est parfois présenté comme « l'équivalent de sed pour JSON ». Ce n'est pas faux, puisqu'il permet en effet des opérations simples depuis la ligne de commande. Mais c'est assez court, comme description : jq est bien plus que cela, il inclut même un langage de programmation complet.

Commençons par quelques exemples simples. On va utiliser le JSON produit en utilisant l'outil madonctl pour récupérer des messages (les pouètes) envoyées sur Mastodon. Si je fais :

%  madonctl --output json timeline :afnic
[{"id":3221183,"uri":"tag:mastodon.social,2017-07-20:objectId=13220936:objectType=Status","url":"https://mastodon.soc
ial/users/afnic/updates/3830115","account":{"id":37388,"username":"afnic","acct":"afnic@mastodon.social","display_nam
e":"Afnic","note":"\u003cp\u003eLe registre des noms de domaine en .fr\u003cbr\u003eRdv sur \u003ca href=\"http://www
.afnic.fr/\" rel=\"nofollow noopener\" target=\"_blank\"\u003e   ...
    

J'ai récupéré les pouètes qui parlent de l'AFNIC, sous forme d'une seule ligne de JSON, peu lisible. Alors qu'avec jq :

%  madonctl --output json timeline :afnic | jq . 
[
  {
    "id": 3221183,
    "uri": "tag:mastodon.social,2017-07-20:objectId=13220936:objectType=Status",
    "url": "https://mastodon.social/users/afnic/updates/3830115",
    "account": {
      "id": 37388,
      "username": "afnic",
      "acct": "afnic@mastodon.social",
      "display_name": "Afnic",
      "note": "<p>Le registre des noms de domaine en .fr<br>Rdv sur <a href=\"http://www.afnic.fr/\" rel=\"nofollow n
oopener\" target=\"_blank\"><span class=\"invisible\">http://www.</span><span class=\"\">afnic.fr/</span><span class=
\"invisible\"></span></a></p>",
      "url": "https://mastodon.social/@afnic",

    

J'ai au contraire du beau JSON bien lisible. C'est la première utilisation de jq pour beaucoup d'utilisateurs, dans un monde où les services REST fabriquent en général du JSON très compact (peut-être pour gagner quelques octets), jq peut servir de pretty-printer.

Le point après la commande jq est un filtre. jq fonctionne par enchainement de filtres et, par défaut, produit des données JSON joliment formatées. (Si, par contre, on ne veut pas de pretty-printing, on lance jq avec l'option --compact-output.)

Prenons un exemple où on veut sélectionner une seule information dans un fichier JSON. Mettons qu'on utilise RDAP (RFC 7482) pour trouver de l'information sur une adresse IP. RDAP produit du JSON :

% curl -s  http://rdap.db.ripe.net/ip/2001:1470:8000:53::44
{
  "handle" : "2001:1470:8000::/48",
  "startAddress" : "2001:1470:8000::/128",
  "endAddress" : "2001:1470:8000:ffff:ffff:ffff:ffff:ffff/128",
  "ipVersion" : "v6",
  "name" : "ARNES-IPv6-NET",
  "type" : "ASSIGNED",
  "country" : "SI",
...
    

Si on ne veut que le pays du titulaire d'un préfixe d'adresses IP, c'est simple avec jq, avec le filtre .country qui veut dire « le champ country de l'objet JSON (. étant le texte JSON de plus haut niveau, ici un objet) » :

% curl -s  http://rdap.db.ripe.net/ip/131.111.150.25 | jq .country
"GB"

% curl -s  http://rdap.db.ripe.net/ip/192.134.1.1 | jq .country
"FR"

On n'aurait pas pu utiliser les outils ligne de commande classiques comme grep ou sed car le format JSON n'est pas orienté ligne (et a trop de subtilités pour eux).

Et si on veut interroger son nœud Bitcoin pour connaitre à quel bloc il en est :

% bitcoin-cli getinfo | jq .blocks
478280

Cet exemple particulier n'est pas super-intéressant, on a déjà bitcoin-cli getblockcount mais cela montre bien qu'on peut utiliser jq très simplement, pour des tâches d'administration système.

Au passage, pour trouver le filtre à utiliser, il faut connaitre la structure du fichier JSON. On peut lire la documentation (dans l'exemple RDAP ci-dessus, c'est le RFC 7483) mais c'est parfois un peu compliqué alors que JSON, étant un format texte, fournit une solution plus simple : afficher le début du fichier JSON et repérer les choses qui nous intéressent. (Un format binaire comme CBOR, RFC 7049, ne permet pas cela.) Cette méthode peut sembler du bricolage, mais l'expérience prouve que les services REST n'ont pas toujours de documentation ou, lorsqu'ils en ont, elle est souvent fausse. Donc, savoir explorer du JSON est utile.

Notez que jq produit du JSON, donc les chaînes de caractères sont entre guillemets. Si on veut juste le texte, on utilise l'option --raw-output :

% curl -s  http://rdap.db.ripe.net/ip/2001:1470:8000:53::44 | jq --raw-output .country
SI
    

Et si on veut un membre d'un objet qui est lui-même un membre de l'objet de plus haut niveau ? On crée un filtre avec les deux clés :

%  echo '{"foo": {"zataz": null, "bar": 42}}' | jq .foo.bar
42
    

Un exemple réel d'un tel « double déréférencement » serait avec l'API de GitHub, ici pour afficher tous les messages de commit :

      
% curl -s https://api.github.com/repos/bortzmeyer/check-soa/commits | \
     jq --raw-output '.[].commit.message'
New rules for the Go linker (see <http://stackoverflow.com/questions/32468283/how-to-contain-space-in-value-string-of-link-flag-when-using-go-build>)
Stupid regression to bug #8. Fixed.
Timeout problem with TCP
Query the NN RRset with EDNS. Closes #9
Stupidest bug possible: I forgot to break the loop when we got a reply
TCP support
...

    

Et si le texte JSON était formé d'un tableau et pas d'un objet (rappel : un objet JSON est un dictionnaire) ? jq permet d'obtenir le nième élément d'un tableau (ici, le quatrième, le premier ayant l'indice 0) :

% echo '[1, 2, 3, 5, 8]' | jq '.[3]'
5
    

(Il a fallu mettre le filtre entre apostrophes car les crochets sont des caractère spéciaux pour le shell Unix.)

Revenons aux exemples réels. Si on veut le cours du Bitcoin en euros, CoinDesk nous fournit une API REST pour cela. On a juste à faire un triple accès :

% curl -s https://api.coindesk.com/v1/bpi/currentprice.json | jq .bpi.EUR.rate
"2,315.7568"
    

Notez que le cours est exprimé sous forme d'une chaîne de caractères, pas d'un nombre, et qu'il n'est pas à la syntaxe correcte d'un nombre JSON. C'est un des inconvénients de JSON : ce format est un tel succès que tout le monde en fait, et souvent de manière incorrecte. (Sinon, regardez une solution sans jq mais avec sed.)

Maintenant, on veut la liste des comptes Mastodon qui ont pouèté au sujet de l'ANSSI. Le résultat de la requête madonctl --output json timeline :anssi est un tableau. On va devoir itérer sur ce tableau, ce qui se fait avec le filtre [] (déjà utilisé plus haut dans l'exemple GitHub), et faire un double déréférencement, le membre account puis le membre acct :

% madonctl --output json timeline :anssi | jq '.[].account.acct'
"nschont@mastodon.etalab.gouv.fr"
"ChristopheCazin@mastodon.etalab.gouv.fr"
"barnault@mastodon.etalab.gouv.fr"
...
    

Parfait, on a bien notre résultat. Mais le collègue qui avait demandé ce travail nous dit qu'il faudrait éliminer les doublons, et trier les comptes par ordre alphabétique. Pas de problème, jq dispose d'un grand nombre de fonctions pré-définies, chaînées avec la barre verticale (ce qui ne déroutera pas les utilisateurs Unix) :

    
%  madonctl --output json timeline :anssi | jq '[.[].account.acct] | unique | .[]' 
"ChristopheCazin@mastodon.etalab.gouv.fr"
"G4RU"
"Sangokuss@framapiaf.org"
...

Oulah, ça devient compliqué ! unique se comprend sans difficulté mais c'est quoi, tous ces crochets en plus ? Comme unique travaille sur un tableau, il a fallu en fabriquer un : les crochets extérieurs dans l'expression [.[].account.acct] disent à jq de faire un tableau avec tous les .account.acct extraits. (On peut aussi fabriquer un objet JSON et je rappelle que objet = dictionnaire.) Une fois ce tableau fait, unique peut bien travailler mais le résultat sera alors un tableau :

   
%  madonctl --output json timeline :anssi | jq '[.[].account.acct] | unique'
[
  "ChristopheCazin@mastodon.etalab.gouv.fr",
  "G4RU",
  "Sangokuss@framapiaf.org",
...

Si on veut « aplatir » ce tableau, et avoir juste une suite de chaînes de caractères, il faut refaire une itération à la fin (le .[]).

(Les Unixiens expérimentés savent qu'il existe uniq et sort comme commandes Unix et qu'on aurait aussi pu faire jq '.[].account.acct' | sort | uniq. Chacun ses goûts. Notez aussi qu'il n'y a pas besoin d'un tri explicite en jq : unique trie le tableau avant d'éliminer les doublons.)

J'ai dit un peu plus haut que jq pouvait construire des textes JSON structurés comme les tableaux. Ça marche aussi avec les objets, en indiquant la clé et la valeur de chaque membre. Par exemple, si je veux un tableau dont les éléments sont des objets où la clé href désigné l'URL d'un pouète Mastodon (ici, les pouètes ayant utilisé le mot-croisillon « slovénie ») :

%  madonctl --output json timeline :slovénie | jq "[.[] | { href: .url }]" 
[
  {
    "href": "https://mastodon.gougere.fr/users/bortzmeyer/updates/40282"
  },
  {
    "href": "https://mamot.fr/@Nighten_Dushi/3131270"
  },
  {
    "href": "https://mastodon.cx/users/DirectActus/updates/29810"
  }
]
    

Les accolades entourant la deuxième partie du programme jq indiquent de construire un objet, dont on indique comme clé href et comme valeur le membre url de l'objet original.

Rappelez-vous, jq ne sert pas qu'à des filtrages ultra-simples d'un champ de l'objet JSON. On peut écrire de vrais programmes et il peut donc être préférable de les mettre dans un fichier. (Il existe évidemment un mode Emacs pour éditer plus agréablement ces fichiers source, jq-mode.) Si le fichier accounts.jq contient :

    
# From a Mastodon JSON file, extract the accounts
[.[].account.acct] | unique | .[]

Alors, on pourra afficher tous les comptes (on se ressert de --raw-output pour ne pas avoir d'inutiles guillemets) :

%  madonctl --output json timeline :anssi | jq --raw-output --from-file accounts.jq 
ChristopheCazin@mastodon.etalab.gouv.fr
G4RU
Sangokuss@framapiaf.org
...

Mais on peut vouloir formater des résultats sous une forme de texte, par exemple pour inclusion dans un message ou un rapport. Reprenons notre nœud Bitcoin et affichons la liste des pairs (Bitcoin est un réseau pair-à-pair), en les classant selon le RTT décroissant. On met ça dans le fichier bitcoin.jq :

"You have " + (length | tostring) + " peers. From the closest (network-wise) to the furthest away",
       ([.[] | {"address": .addr, "rtt": .pingtime}] | sort_by(.rtt) | 
       .[] | ("Peer " + .address + ": " + (.rtt|tostring) + " ms"))
    

Et on peut faire :

%   bitcoin-cli getpeerinfo  | jq --raw-output --from-file bitcoin.jq
You have 62 peers. From the closest (network-wise) to the furthest away
Peer 10.105.127.82:38806: 0.00274 ms
Peer 192.0.2.130:8333: 0.003272 ms
Peer [2001:db8:210:5046::2]:33567: 0.014099 ms
...
    

On a utilisé des constructions de tableaux et d'objets, la possibilité de trier sur un critère quelconque (ici la valeur de rtt, en paramètre à sort_by) et des fonctions utiles pour l'affichage de messages : le signe plus indique la concaténation de chaînes, et la fonction tostring transforme un entier en chaîne de caractères (jq ne fait pas de conversion de type implicite).

jq a également des fonctions pré-définies pour faire de l'arithmétique. Utilisons-les pour analyser les résultats de mesures faites par des sondes RIPE Atlas. Une fois une mesure lancée (par exemple la mesure de RTT #9211624, qui a été créée par la commande atlas-reach -r 100 -t 1 2001:500:2e::1, cf. mon article « Using RIPE Atlas to Debug Network Connectivity Problems »), les résultats peuvent être récupérés sous forme d'un fichier JSON (à l'URL https://atlas.ripe.net/api/v2/measurements/9211624/results/). Cherchons d'abord le RTT maximal :

% jq '[.[].result[0].rtt] | max' < 9211624.json 
505.52918
    

On a pris le premier élément du tableau car, pour cette mesure, chaque sonde ne faisait qu'un test. Ensuite, on utilise les techniques jq déjà vues, plus la fonction max (qui prend comme argument un tableau, il a donc fallu construire un tableau). La sonde la plus lointaine de l'amer est donc à plus de 500 millisecondes. Et le RTT minimum ? Essayons la fonction min

% jq '[.[].result[0].rtt] | min' < 9211624.json
null
    

Ah, zut, certaines sondes n'ont pas réussi et on n'a donc pas de RTT, ce que jq traduit en null. Il faut donc éliminer du tableau ces échecs. jq permet de tester une valeur, avec select. Par exemple, (select(. != null) va tester que la valeur existe. Et une autre fonction jq, map, bien connue des programmeurs qui aiment le style fonctionnel, permet d'appliquer une fonction à tous les éléments d'un tableau. Donc, réessayons :

% jq '[.[].result[0].rtt] | map(select(. != null)) | min' < 9211624.json
1.227755
    

C'est parfait, une milli-seconde pour la sonde la plus proche. Notez que, comme map prend un tableau en entrée et rend un tableau, on aurait aussi pu l'utiliser au début, à la place des crochets :

% jq 'map(.result[0].rtt) | map(select(. != null)) | min' < 9211624.json
1.227755
    

Et la moyenne des RTT ? On va utiliser les fonctions add (additionner tous les éléments du tableau) et length (longueur du tableau) :

% jq '[.[].result[0].rtt] | add / length' < 9211624.json 
76.49538228
    

(Il y a une faille dans le programme, les valeurs nulles auraient dû être exclues. Je vous laisse modifier le code.) La moyenne n'est pas toujours très utile quand on mesure des RTT. C'est une métrique peu robuste, que quelques outliers suffisent à perturber. Il est en général bien préférable d'utiliser la médiane. Une façon simple de la calculer est de trier le tableau et de prendre l'élément du milieu (ou le plus proche du milieu) :

% jq '[.[].result[0].rtt] | sort | .[length/2]' < 9211624.json
43.853845

On voit que la médiane est bien plus petite que la moyenne, quelques énormes RTT ont en effet tiré la moyenne vers le haut. J'ai un peu triché dans le filtre jq ci-dessus car il ne marche que pour des tableaux de taille paire. Si elle est impaire, length/2 ne donnera pas un nombre entier et on récupérera null. Corrigeons, ce sera l'occasion d'utiliser pour la première fois la structure de contrôle if. Notez qu'une expression jq doit toujours renvoyer quelque chose (les utilisateurs de langages fonctionnels comme Haskell ne seront pas surpris par cette règle), donc pas de if sans une branche else :

% jq '[.[].result[0].rtt] | sort | if length % 2 == 0 then .[length/2] else .[(length-1)/2] end' < 9211624.json  
43.853845

Et maintenant, si on veut la totale, les quatre métriques avec du texte, on mettra ceci dans le fichier source jq. On a utilisé un nouvel opérateur, la virgule, qui permet de lancer plusieurs filtres sur les mêmes données :

map(.result[0].rtt) | "Median: " + (sort |
				    if length % 2 == 0 then .[length/2] else .[(length-1)/2] end |
				    tostring),
                      "Average: " + (map(select(. != null)) | add/length | tostring),
                      "Min: " + (map(select(. != null)) | min | tostring),
                      "Max: " + (max | tostring)
    

Et cela nous donne :

% jq --raw-output --from-file atlas-rtt.jq < 9211624.json     
Median: 43.853845
Average: 77.26806290909092
Min: 1.227755
Max: 505.52918
    

Peut-on passer des arguments à un programme jq ? Évidemment. Voyons un exemple avec la base des « espaces blancs » décrite dans le RFC 7545. On va chercher la puissance d'émission maximale autorisée pour une fréquence donnée, avec ce script jq, qui contient la variable frequency, et qui cherche une plage de fréquences (entre startHz et stopHz) comprenant cette fréquence :

.result.spectrumSchedules[0].spectra[0].frequencyRanges[] |
     select (.startHz <= ($frequency|tonumber) and .stopHz >= ($frequency|tonumber)) |
     .maxPowerDBm     
    

On l'appelle en passant la fréquence où on souhaite émettre :

% jq --from-file paws.jq --arg frequency  5.2E8  paws-chicago.json 
15.99999928972511
    

(Ne l'utilisez pas telle quelle : par principe, les espaces blancs ne sont pas les mêmes partout. Celui-ci était pour Chicago en juillet 2017.)

Il est souvent utile, quand on joue avec des données, de les grouper par une certaine caractéristique, par exemple pour compter le nombre d'occurrences d'un certain phénomène, ou bien pour classer. C'est le classique GROUP BY de SQL, qui a son équivalent en jq. Revenons à la liste des comptes Mastodon qui ont pouèté sur l'ANSSI et affichons combien chacun a émis de pouètes. On va grouper les pouètes par compte, fabriquer un objet JSON dont les clés sont le compte, le trier, puis afficher le résultat. Avec ce code jq :

# Count the number of occurrences
[.[].account] | group_by(.acct) |
                # Create an array of objects {account, number}
                [.[] | {"account": .[0].acct, "number": length}] |
                # Now sort
                sort_by(.number) | reverse | 
                # Now, display
                .[] | .account + ": " + (.number | tostring)
    

On obtiendra :

% madonctl --output json timeline :anssi | jq -r -f   anssi.jq
gapz@mstdn.fr: 4
alatitude77@mastodon.social: 4
nschont@mastodon.etalab.gouv.fr: 2
zorglubu@framapiaf.org: 1
sb_51_@mastodon.xyz: 1
    

Voilà, on a bien avancé et je n'ai toujours pas donné d'exemple avec le DNS. Un autre cas d'utilisation de select va y pourvoir. Le DNS Looking Glass peut produire des résultats en JSON, par exemple ici le SOA du TLD .xxx : curl -s -H 'Accept: application/json' http://dns.bortzmeyer.org/xxx/SOA. Si je veux juste le numéro de série de cet enregistrement SOA ?

% curl -s -H 'Accept: application/json' http://dns.bortzmeyer.org/xxx/SOA | \
    jq '.AnswerSection[0].Serial'
2011210193
    

Mais il y a un piège. En prenant le premier élément de la Answer Section, j'ai supposé qu'il s'agissait bien du SOA demandé. Mais l'ordre des éléments dans les sections DNS n'est pas défini. Par exemple, si une signature DNSSEC est renvoyée, elle sera peut-être le premier élément. Il faut donc être plus subtil, et utiliser select pour ne garder que la réponse de type SOA :

% curl -s -H 'Accept: application/json' http://dns.bortzmeyer.org/xxx/SOA | \
    jq '.AnswerSection | map(select(.Type=="SOA")) | .[0].Serial'
2011210193

Notre code jq est désormais plus robuste. Ainsi, sur un nom qui a plusieurs adresses IP, on peut ne tester que celles de type IPv6, ignorant les autres, ainsi que les autres enregistrements que le serveur DNS a pu renvoyer :

% curl -s -H 'Accept: application/json' http://dns.bortzmeyer.org/gmail.com/ADDR | \
    jq '.AnswerSection | map(select(.Type=="AAAA")) | .[] | .Address' 
"2607:f8b0:4006:810::2005"
    

Bon, on approche de la fin, vous devez être fatigué·e·s, mais encore deux exemples, pour illustrer des trucs et concepts utiles. D'abord, le cas où une clé dans un objet JSON n'a pas la syntaxe d'un identificateur jq. C'est le cas du JSON produit par le compilateur Solidity. Si je compile ainsi :

% solc  --combined-json=abi registry.sol > abi-registry.json

Le JSON produit a deux défauts. Le premier est que certains utilisateurs ont une syntaxe inhabituelle (des points dans la clé, et le caractère deux-points) :

{"registry.sol:Registry": {"abi": ...
  

jq n'accepte pas cette clé comme filtre :

%  jq '.contracts.registry.sol:Registry.abi' abi-registry.json  
jq: error: syntax error, unexpected ':', expecting $end (Unix shell quoting issues?) at <top-level>, line 1:
.contracts.registry.sol:Registry.abi                       
jq: 1 compile error
  

Corrigeons cela en mettant la clé bizarre entre guillemets :

% jq '.contracts."registry.sol:Registry".abi' abi-registry.json
  

On récupère ainsi l'ABI du contrat. Mais, et là c'est gravement nul de la part du compilateur, l'ABI est une chaîne de caractères à évaluer pour en faire du vrai JSON ! Heureusement, jq a tout ce qu'il faut pour cette évaluation, avec la fonction fromjson :

% jq '.contracts."registry.sol:Registry".abi | fromjson' abi-registry.json

Ce problème des clés qui ne sont pas des identificateurs à la syntaxe traditionnelle se pose aussi si l'auteur du JSON a choisi, comme la norme le lui permet, d'utiliser des caractères non-ASCII pour les identificateurs. Prenons par exemple le fichier JSON des verbes conjugués du français, en https://github.com/Drulac/Verbes-Francais-Conjugues. Le JSON est :

[{ "Indicatif" : { "Présent" : [ "abade", "abades", "abade", "abadons", "abadez", "abadent" ], "Passé composé" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], "Imparfait" : [ "abadais", "abadais", "abadait", "abadions", "abadiez", "abadaient" ], "Plus-que-parfait" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], "Passé simple" : [ "abadai", "abadas", "abada", "abadâmes", "abadâtes", "abadèrent" ], "Passé antérieur" : [ "abadé", "abadé", "abadé", "abadé", "abadé", "abadé" ], ...
    

Il faut donc mettre les clés non-ASCII entre guillemets. Ici, la conjugaison du verbe « reposer » :

% jq 'map(select(.Infinitif."Présent"[0] == "reposer"))' verbs.json
[
  {
    "Indicatif": {
      "Présent": [
        "repose",
        "reposes",
        "repose",
   ...
    

Notez toutefois que jq met tout en mémoire. Cela peut l'empêcher de lire de gros fichiers :

% ls -lh la-crime.json 
-rw-r--r-- 1 stephane stephane 798M Sep  9 19:09 la-crime.json

% jq .data la-crime.json     
error: cannot allocate memory
zsh: abort      jq .data la-crime.json
   

Et un dernier exemple, pour montrer les manipulations de date en jq, ainsi que la définition de fonctions. On va utiliser l'API d'Icinga pour récupérer l'activité de la supervision. La commande curl -k -s -u operator:XXXXXXX -H 'Accept: application/json' -X POST 'https://ADRESSE:PORT/v1/events?types=StateChange&queue=operator' va nous donner les changements d'état des machines et des services supervisés. Le résultat de cette commande est du JSON : on souhaite maintenant le formater de manière plus compacte et lisible. Un des membres JSON est une date écrite sous forme d'un nombre de secondes depuis une epoch. On va donc la convertir en jolie date avec la fonction todate. Ensuite, les états Icinga (.state) sont affichés par des nombres (0 = OK, 1 = Avertissement, etc.), ce qui n'est pas très agréable. On les convertit en chaînes de caractères, et mettre cette conversion dans une fonction, s2n :

def s2n(s):
  if s==0 then "OK" else if s==1 then "Warning" else "Critical" end  end;
(.timestamp | todate) + " " + .host + " " + .service + " " +  " " + s2n(.state) +
    " " + .check_result.output

Avec ce code, on peut désormais exécuter :

% curl -k -s -u operator:XXXXXXX -H 'Accept: application/json' -X POST 'https://ADRESSE:PORT/v1/events?types=StateChange&queue=operator' | \
   jq --raw-output --unbuffered --from-file logicinga.jq
...
2017-08-08T19:12:47Z server1 example Critical HTTP CRITICAL: HTTP/1.1 200 OK - string 'Welcome' not found on 'http://www.example.com:80/' - 1924 bytes in 0.170 second response time 

Quelques lectures pour aller plus loin :

Merci à Jean-Edouard Babin et Manuel Pégourié-Gonnard pour la correction d'une grosse bogue dans la première version de cet article.


L'article seul

RFC 8198: Aggressive Use of DNSSEC-Validated Cache

Date de publication du RFC : Juillet 2017
Auteur(s) du RFC : K. Fujiwara (JPRS), A. Kato (Keio/WIDE), W. Kumari (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 26 juillet 2017


Lorqu'un client DNS interroge un résolveur, celui-ci peut répondre très vite si l'information demandée est dans sa mémoire (son « cache ») mais cela peut être beaucoup plus lent s'il faut aller demander aux serveurs faisant autorité. Il est donc essentiel, pour les performances du DNS, d'utiliser le cache le plus possible. Traditionnellement, le résolveur n'utilisait le cache que si la question posée par le client avait une réponse exacte dans le cache. Mais l'arrivée de DNSSEC autorise un usage plus énergique et plus efficace du cache : ce nouveau RFC permet aux résolveurs de synthétiser des réponses à partir des informations DNSSEC.

Comment peut-il faire ? Eh bien prenons par exemple les enregistrements NSEC. Ils indiquent une plage où il n'y a pas de noms enregistrés. Ainsi, si une zone example.com contient :

albatross IN AAAA 2001:db8:1028::a:1
elephant  IN AAAA 2001:db8:1028::a:2
zebra     IN AAAA 2001:db8:1028::a:3
    

Sa signature DNSSEC va ajouter des enregistrements NSEC, notamment :

albatross.example.com. IN NSEC elephant.example.com. AAAA    

qui veut dire qu'il n'y a pas de nom entre albatross et elephant. Si le client interroge le résolveur à propos de cat.example.com, le résolveur ira voir les serveurs faisant autorité, il aura une réponse négative (NXDOMAIN, ce nom n'existe pas) et l'enregistrement NSEC. Les deux informations seront renvoyées au client, et pourront être mémorisées dans le cache. Maintenant, si le client demande dog.example.com, les résolveurs traditionnels retourneront demander aux serveurs faisant autorité. Alors que les résolveurs modernes, conformes à ce nouveau RFC 8198, pourront déduire du NSEC que dog.example.com n'existe pas non plus, et immédiatement générer un NXDOMAIN pour le client. Cela fera gagner du temps et des efforts à tout le monde. (Les règles exactes sont dans la section 5.1 de notre RFC.)

La règle (désormais dépassée) comme quoi le résolveur ne peut répondre immédiatement que s'il a l'information correspondant à la question exacte est spécifiée dans le RFC 2308, pour le cache négatif (mémoriser les réponses NXDOMAIN). Désormais, elle n'est plus obligatoire si (et seulement si, voir section 9) le résolveur valide avec DNSSEC, et si la zone est signée (aucun changement si elle ne l'est pas). Outre le cas simple de NSEC avec des réponses négatives, présenté plus haut, il y a deux cas plus complexes, les enregistrements NSEC3, et les jokers. Si la zone est signée avec NSEC3 au lieu de NSEC, les enregistrements NSEC3 indiqueront des condensats et pas des noms et le résolveur devra donc condenser le nom demandé, pour voir s'il tombe dans un NSEC3 connu, et si on peut synthétiser le NXDOMAIN. Les règles exactes sur les NSEC3 sont dans le RFC 5155 (attention, c'est compliqué), sections 8.4 à 8.7, et dans notre RFC, section 5.2. Évidemment, si la zone est signée avec l'option « opt-out » (c'est le cas de la plupart des TLD), le résolveur ne peut pas être sûr qu'il n'y a pas un nom non signé dans une plage indiquée par un enregistrement NSEC, et ne peut donc pas synthétiser. Tout aussi évidemment, toute solution qui empêchera réellement l'énumération des noms dans une zone signée (comme le projet NSEC5) sera incompatible avec cette solution.

Si la zone inclut des jokers (RFC 1034, section 4.3.3), par exemple example.org :

    
avocado   IN A 192.0.2.1
*         IN A 192.0.2.2
zucchini IN A 192.0.2.3

Alors, le résolveur pourra également synthétiser des réponses positives. S'il a déjà récupéré et mémorisé le joker, et que le client lui demande l'adresse IPv4 de leek.example.org, le résolveur pourra tout de suite répondre 192.0.2.2. (J'ai simplifié : le résolveur doit aussi vérifier que le nom leek.example.org n'existe pas, ou en tout cas n'a pas d'enregistrement A. Le joker ne masque en effet pas les noms existants. Détails en section 5.3 du RFC.)

Pour cette utilisation plus énergique de la mémoire d'un résolveur validant, il a fallu amender légèrement le RFC 4035, dont la section 4.5 ne permettait pas cette synthèse de réponses (la nouvelle rédaction est en section 7). Notez un inconvénient potentiel de cette synthèse : un nom ajouté ne sera pas visible tout de suite. Mais c'est de toute façon une propriété générale du DNS et de ses caches souvent appelée, par erreur, « durée de propagation ».

La durée exacte pendant laquelle le résolveur « énergique » pourra garder les informations qui lui servent à la synthèse des réponses est donnée par le TTL des NSEC.

Quel intérêt à cette synthèse énergique de réponses ? Comme indiqué plus haut, cela permet de diminuer le temps de réponse (section 6). Du fait de la diminution de nombre de questions à transmettre, cela se traduira également par une diminution de la charge, et du résolveur, et du serveur faisant autorité. Sur la racine du DNS, comme 65 % des requêtes entrainent un NXDOMAIN (voir par exemple les statistiques de A-root), le gain devrait être important. Un autre avantage sera pour la lutte contre les attaques dites « random QNAMEs » lorsque l'attaquant envoie plein de requêtes pour des noms aléatoires (générant donc des NXDOMAIN). Si l'attaquant passe par un résolveur, celui-ci pourra écluser la grande majorité des requêtes sans déranger le serveur faisant autorité.

Mais la synthèse énergique entraine aussi un gain en matière de vie privée (cf. RFC 7626) : les serveurs faisant autorité verront encore moins de questions.

Cette technique de « cache négatif énergique » avait été proposée pour la première fois dans la section 6 du RFC 5074. Elle s'inscrit dans une série de propositions visant à augmenter l'efficacité des caches DNS, comme le « NXDOMAIN cut » du RFC 8020. La synthèse énergique de réponses prolonge le RFC 8020, en allant plus loin (mais elle nécessite DNSSEC).

Il semble que Google Public DNS mette déjà en œuvre une partie (les réponses négatives) de ce RFC, mais je n'ai pas encore vérifié personnellement.


Téléchargez le RFC 8198


L'article seul

Conférence climagic sur la ligne de commande et ses beautés

Première rédaction de cet article le 20 juillet 2017


Le 19 juillet 2017, à Prague, Mark Krenz, le fondateur et animateur du fameux compte Twitter climagic (Command-Line interface magic), profitait de ses vacances en Europe pour faire un exposé sur la ligne de commande, ses beautés, et le projet climagic. Ça m'a fait rater la séance plénière de l'IETF 99 mais ça valait la peine. (Pour ce·ux·lles qui n'étaient pas à Prague, les pauvres, l'exposé est visible chez Google.)

L'exposé se tenait à la ČVUT (excellents locaux, vue magnifique sur la ville depuis le 14e étage). La grande salle était presque pleine. Comme on s'en doute, il n'y avait que trois ou quatre femmes.

La thèse centrale de Mark Krenz est que la ligne de commande n'est pas une survivance du passé, qu'on garde par habitude, ou par nostalgie. C'est toujours un outil puissant et efficace pour les professionnels (comme un de ses enfants lui avait dit une fois, lorsque le réseau était en panne, « open the black window and type text, to fix the network »). Le concept n'est en outre pas spécifique à Unix, Mark Krenz a cité l'ARexx d'Amiga. Une des preuves de cette pertinence de la ligne de commande est que désormais même Windows a une ligne de commandes de qualité raisonnable, Powershell. L'orateur a ensuite illustré ce point de vue de nombreux exemples.

Prévoyant que l'unixien moyen était pinailleur, il a prévenu tout de suite que c'était ses exemples, qu'il y avait plusieurs façons de faire, et que ce n'était pas la peine de lui dire que tel exemple aurait pu être fait avec telle autre méthode. Il a aussi précisé que la référence pour ses tweets était Linux avec bash (même s'il teste parfois sur une VM FreeBSD) et qu'il ne fallait pas l'embêter avec des « mais c'est pas portable ». Effectivement, il utilise des exemples comme la commande interne help (specifique à bash) ou comme l'excellente et indispensable option -i de sed (spécifique à GNU sed).

Mark Krenz (qui travaille au CACR et est l'auteur des excellents num-utils) a d'abord résumé le projet climagic. Démarré en 2009, il consiste en un compte Twitter (il y a aussi des vidéos) où environ la moitié des tweets sont des commandes Unix. Elles sont en général sous le format commande # description (le croisillon indiquant un commentaire). Par exemple :

killall -USR1 dd # Force each dd command in the process table to output its current status (blocks written, etc). The USR1 signal does this.
    

(lisez les documentations de killall et dd si nécessaire.) Les autres tweets sont diverses réflexions, ou des blagues, par exemple sur Chuck Norris (« Chuck Norris once ran cd .. in / and it worked »).

Ah, et pourquoi le magic dans « climagic ». Précisement parce que cela n'a rien de magique, tout peut être appris. « Witchcraft to the ignorant, simple science for the learned one » (Leigh Brackett).

La force du shell Unix est évidemment sa capacité à combiner des commandes pour faire davantage que ce que ferait chaque commande individuelle. Donc, lister des commandes et des options, c'est bien, mais il faut surtout se rappeler qu'on peut les combiner. Un exemple montré par l'auteur est une commande qui énonce à voix haute les réseaux Wi-Fi rencontrés : iwlist wlp2s0 scan | awk -F: '/ESSID/ {print $2}' | sort|uniq | espeak.

Je ne vais pas citer tous les trucs Unix qu'a montré l'orateur, uniquement ceux que je ne connaissais pas. Vous pourrez ainsi juger de la profondeur de mon ignorance d'Unix :

  • Je n'utilise pas les opérateurs rigolos après les noms de variables du shell. Si ALPHABET vaut les lettres de l'alphabet (par exemple en faisant ALPHABET=$(echo {a..z} | tr -d ' '), alors echo ${ALPHABET:0:3} affichera les trois premières, et echo ${ALPHABET^^} affichera leurs versions majuscules (bash seulement).
  • Je ne connaissais pas certaines variables spéciales comme SHLVL (pour voir si on est dans un sous-shell), ou COLUMNS (qui était inutile sur un VT100 mais bien pratique sur un xterm).
  • J'ai découvert qu'après more et less, il y avait désormais une commande most (pas mal pour les fichiers binaires).
  • column est cool pour formater joliment des commandes qui ne le font pas elle-même (comme mount).
  • Il y a d'autres commandes que je ne connaissais pas mais qui ne m'ont pas convaincu comme comm, ou pee (analogue à tee mais envoie les données à un processus, pas à un fichier, il se trouve dans les moreutils).
  • Certaines commandes classiques donnent des résultats inattendus dans certains cas. Par exemple cal 9 1752. Si vous vous demandez ce que sont devenus ces treize jours, ils ont disparu suite à l'application de la réforme grégorienne à l'empire britannique (les pays catholiques l'ont fait longtemps avant, ce dont on peut déduire qu'Unix n'est pas catholique). Comme tous les pays n'ont pas adopté cette réforme au même moment, logiquement, le résultat devrait dépendre de la valeur de la variable d'environnement LC_CTIME mais ce n'est hélas pas le cas.
  • Je ne savais pas non plus que la barre oblique après un joker ne renvoyait que les répertoires. Parfait pour un du -sh */ (au fait, GNU sort peut trier ces tailles « human-readables », avec -h : du -sh */ | sort -rh | head -5 permet de voir les cinq plus gros répertoires, avec un joli affichage des tailles.)
  • Parmi les autres options pratiques, -P dit à xargs d'effectuer les commandes en parallèle (comme le -j de make). Cela a permis à l'orateur de montrer un fping refait avec un ping ordinaire, l'expansion des séquences, et xargs.

Lors de la discussion qui a suivi, j'ai soulevé la question d'Unicode dans le shell Unix. Certaines commandes gèrent bien Unicode, par exemple awk :

%  echo -n café | awk '{print length($1)}'
4
    

Il a bien indiqué la longueur en caractères, pas en octets (« café » fait cinq octets en UTF-8). wc, lui, permet d'avoir caractères ou octets au choix :

% echo -n café | wc -m
4
% echo -n café | wc -c
5      
    

Cela montre que, même si le shell a la réputation d'être un vieux truc, il a su s'adapter à la modernité. Mais ce n'est pas le cas de toutes les commandes Unix. Au moins sur mon Ubuntu (il parait que ça marche sur d'autres Unix), cut semble insensible à l'internationalisation (il affiche les octets bêtement, malgré ce que prétend sa documentation) :

% echo caféthé | cut -c 3
f
% echo caféthé | cut -c 4
�
% echo caféthé | cut -c 5
�
% echo caféthé | cut -c 6     
    

De même, l'expansion par le shell ne semble pas marcher avec les caractères non-ASCII :

% echo {a..c}
a b c
% echo {à..ç}
{à..ç}
    

(Et, oui, la variable d'environnement LC_CTYPE était bien définie, sinon, grep ou wc n'auraient pas marché.)

M. climagic a évidemment parlé de plein d'autres choses, mais je les connaissais. Et vous ?

  • Il a bien sûr cité man et ses options. Notez que man -k delete ne trouve pas la commande rm
  • Il a longuement parlé de nombreux trucs du shell comme les astuces de la commande cd (cd - vous ramène au dernier répertoire), le job control (Control-Z, fg et bg), l'expansion des intervalles (echo {a..z}, echo IMG_{3200..3300}.jpg, qui permet de faire un rsync -a -v IMG_{3200..3300}.jpg server:dir…, les fonctions (le plus bel exemple était une redéfinition de cd, pour accepter l'argument ... pour dire « remonte de deux niveaux » : function cd(){ [[ "$1" == "..." ]] && builtin cd ../.. || builtin cd $@}, la redirection des seules erreurs (command 2> file), etc.
  • Mark Krenz a évidemment consacré du temps à awk et sed (tiens, lui non plus n'a pas l'air de gérer Unicode).
  • Et enfin il a parlé de deux programmes géniaux qui méritent d'être plus connus, ngrep (comme grep mais pour le réseau), et socat (netcat en mieux).

Autre discussion à la fin de la réunion, je lui ai demandé pourquoi ne pas avoir également climagic sur le réseau social décentralisé Mastodon. Il n'est pas contre (surtout si on peut le faire depuis la ligne de commande, bien sûr, mais c'est possible avec madonctl), c'est surtout un problème de manque de temps. (À noter que climagic a existé sur identi.ca mais que cela ne lui a pas laissé de bons souvenirs.)

La vue depuis l'université : prague-from-tu.jpg

Le panneau sur la salle de réunion : climagic-room.jpg


L'article seul

RFC 8197: A SIP Response Code for Unwanted Calls

Date de publication du RFC : Juillet 2017
Auteur(s) du RFC : H. Schulzrinne (FCC)
Chemin des normes
Première rédaction de cet article le 18 juillet 2017


Voici un RFC qui n'est pas trop long à lire ou à comprendre : il ajoute un code de réponse au protocole SIP (utilisé surtout pour la téléphonie sur IP), le code 607 qui dit explicitement « je n'ai pas envie de répondre à cet appel ».

Sur certaines lignes téléphoniques, la majorité des appels entrants sont du spam (cf. RFC 5039), du genre salons de beauté ou remplacements de fenêtres, voire appels automatiques en faveur d'un candidat politicien. Toute méthode de lutte contre ce spam nécessite d'avoir un retour des utilisateurs, qui déclarent « je ne veux pas de cet appel ». Simplement raccrocher ne suffit pas, il faut permettre à l'utilisateur de déclarer explicitement la spamicité d'un appel. (Les codes de rejet existants comme 603 - RFC 3261, section 21.6.2 - ne sont pas assez spécifiques. 603 indique typiquement un rejet temporaire, du genre je suis à table, ne me dérangez pas maintenant.)

C'est désormais possible avec ce nouveau code de retour 607, enregistré à l'IANA. Le logiciel de l'utilisateur enverra ce code (il aura typiquement un bouton « rejet de ce spam » comme c'est le cas pour les logiciels de courrier), et il pourra être utilisé pour diverses techniques anti-spam (par exemple par un fournisseur SIP pour déterminer que tel numéro ne fait que du spam et peut donc être bloqué). La FCC (pour laquelle travaille l'auteur du RFC) avait beaucoup plaidé pour un tel mécanisme.

Notez que l'interprétation du code sera toujours délicate, car il peut y avoir usurpation de l'identité de l'appelant (section 6 de notre RFC, et aussi RFC 4474) ou tout simplement réaffectation d'une identité à un nouvel utilisateur, innocent de ce qu'avait fait le précédent. Au passage, dans SIP, l'identité de l'appelant peut être un URI SIP ou bien un numéro de téléphone, cf. RFC 3966.

Le code aurait dû être 666 (le nombre de la Bête) mais le code 607 a été finalement choisi pour éviter des querelles religieuses. (Le RFC 7999 n'a pas eu les mêmes pudeurs.)

L'action (ou l'inaction) suivant une réponse 607 dépendra de la politique de chaque acteur. (La discussion animée à l'IETF avait surtout porté sur ce point.) Comme toujours dans la lutte contre le spam, il faudra faire des compromis entre trop de faux positifs (on refuse des messages légitimes) et trop de faux négatifs (on accepte du spam). D'autant plus que l'utilisateur peut être malveillant (rejeter comme spam des appels légitimes dans l'espoir de faire mettre l'appelant sur une liste noire) ou simplement incompétent (il clique un peu au hasard). Les signalements sont donc à prendre avec des pincettes (section 6 du RFC).


Téléchargez le RFC 8197


L'article seul

Developing a dnstap to C-DNS converter at the IETF hackathon

First publication of this article on 17 July 2017


The weekend of 15-16 july 2017, I participated to the IETF 99 hackathon in Prague. The project was to develop a dnstap to C-DNS converter. This is a small documentation of the result and of the lessons learned.

First, a bit of background. Most DNS operators these days gather a lot of data from the DNS traffic and then analyze it. Events like DITL can fill many hard disks with pcap files. pcap not being very efficient (both in producing it, in storing it, and in analyzing it), a work is under way at the IETF to create a better format: C-DNS (for "capture DNS"). C-DNS is currently specified in an Internet-Draft, draft-ietf-dnsop-dns-capture-format. It is only a draft, not yet a RFC. One of the goals of IETF hackathons is precisely to test drafts, to see if they are reasonable, implementable, etc, before they are approved.

Note that C-DNS is based on CBOR (RFC 7049). There is already an implementation of C-DNS, available under a free software licence. Here, the idea was to write a second implementation, to test interoperability. The target was a C-DNS producer. Where to find the data to put in the file? I choosed dnstap since it is currently the best way to get data from a DNS server. dnstap relies on protocol buffers so it may be difficult to handle but there is already a good dnstap client, written in Go.

So, the decisions were:

  • Get DNS data via dnstap from a Unbound resolver (the only resolver with dnstap support at this time),
  • Use the dnstap client as a starting point (which means the hackathon project was to use Go),
  • Test the C-DNS produced with the inspector from the first implementation.

First, compiling the first C-DNS implementation, which is in C++ (I used a not-up-to-date Ubuntu machine): it requires several Boost libraries, and I'm too lazy to check exactly which, so I installed everything aptitude install libboost1.58-all-dev liblzma-dev lzma-dev lzma. The C++ capture library libtins does not appear to be in a Ubuntu "xenial" package (but there is a Debian one and it is in some Ubuntu versions). The "trusty" package is too old, we install the "yakkety" one manually:

./configure
make

Then, we can produce C-DNS files from pcaps:

./compactor/compactor -o short.cdns -c /dev/null short.pcap

And read C-DNS files:

./compactor/inspector short.cdns

So, now, we can test the produced files.

Next, compiling Unbound with dnstap support (it is not done by default, probably because it add a lot of dependencies, coming from protocol buffers):

aptitude install libprotobuf-c-dev  protobuf-c-compiler libfstrm-dev fstrm-bin
./configure --enable-dnstap
make

We configure Unbound with:

dnstap:
    dnstap-enable: yes
    dnstap-socket-path: "/var/run/unbound/dnstap.sock" # "the dnstap socket won't appear in the filesystem until the dnstap listener is started."
    dnstap-send-identity: yes
    dnstap-send-version: yes
    dnstap-log-resolver-response-messages: yes
    dnstap-log-client-query-messages: yes
    dnstap-log-resolver-query-messages: yes
    dnstap-log-client-response-messages: yes

And we start it:

./unbound/unbound -d -c dnstap.conf

Then, when processing DNS queries and responses, Unbound sends a report to the dnstap socket. We just need a dnstap client to display it.

We forked the dnstap client, created a c-dns branch, and started our copy. To compile it:

aptitude install golang-dns-dev golang-goprotobuf-dev
go get github.com/farsightsec/golang-framestream
go install github.com/bortzmeyer/golang-dnstap
go build dnstap/main.go

Then we can run it:

./main -y -u /var/run/unbound/dnstap.sock

And we can see the YAML output when Unbound receives or sends DNS messages. To store this output, I was not able to put the data in a file, so I just redirected the standard output.

Most of the hackathon work took place in the new file CdnsFormat.go. First, producing CBOR. I had a look at the various Go implementations of CBOR. Most seem old and unmaintained and CBOR seemed to me simple enough that it was faster to reimplement from scratch. This is the code at the beginning of CdnsFormat.go, the cborInteger() and similar functions. I also developed a small Go program to read CBOR files and display them as a tree, a tool which was very useful to debug my C-DNS producer. The grammar of C-DNS is specified in the CDDL language (currently specified in another Internet-Draft). A validating tool exists, taking CDDL and CBOR and telling if the CBOR file is correct, but this tool's error messages are awful. It is just good to say if the file is OK or not, afterwards, you have to examine the file manually.

Then, actually producing the C-DNS file. The C-DNS format is not simple: it is intended for performance, and harsh compression, not for ease of implementation. The basic idea is to capture in tables most of what is repeated, actual DNS messages being made mostly of references to these tables. For instance, a DNS query for www.sinodun.com won't include the string www.sinodun.com. This FQDN will be stored once, in the name-rdata table, and a numeric index to an entry of this table will be used in the message. For instance:

// Server address 
s.Write(cborInteger(0))
s.Write(cborInteger(2))  

Here, we don't write the server IP address directly, we write an index to the table where the IP address is (2 is the second value of the table, C-DNS indexes start at 1, read later about the value zero). Note that the inspector crashed when meeting indexes going outside of an array, something that Jim Hague fixed during the hackathon.

Speaking of IP addresses, "client" and "server" in the C-DNS specification don't mean "source" and "destination". For a DNS query, "client" is the source and "server" the destination but, for a DNS response, it is the opposite (dnstap works the same way, easing the conversion).

Among the things that are not clear in the current version of the draft (version -03) is the fact that CBOR maps are described with keys that are strings, while in the CDNS format, they are actually integers. So when you read in the draft:

FilePreamble = {
      major-format-version => uint, 
      minor-format-version => uint, 
      ...

Read on: FilePreamble is indeed a CBOR map but the actual keys are further away:

major-format-version = 0
minor-format-version = 1

So for a version number of 0.5 (the current one), you must write in Go the map as:

//    Major version
s.Write(cborInteger(0))
s.Write(cborInteger(0))
//    Minor version
s.Write(cborInteger(1))
s.Write(cborInteger(5)) 

Another choice to make: CBOR allow arrays and maps of indefinite of finite length. C-DNS does not specify which one to use, it is up to you, programmer, to choose. Of course, it is often the case that you don't know the length in advance, so sometimes you have no choice. An example of a finite length array:

s.Write(cborArray(3))
// Then write the three items  

And an example of an indefinite length array:

s.Write(cborArrayIndef())
// Write the items
s.Write(cborBreak())

Note that you need to checkpoint from time to time (for instance by rotating the C-DNS file and creating a new one) otherwise a crash will leave you with a corrupted file (no break at the end). The fact that C-DNS (well, CBOR, actually), requires this break code at the end forced us to modify the API of dnstap modules, to have a new function finish (of type TextFinishFunc, this function is a no-op for the other formats).

Note there is a semantic mismatch between C-DNS and dnstap: C-DNS assumes all the data is mandatory while in dnstap, almost everything is optional. That means that we have to create dummy data in some places for instance:

s.Write(cborArray(2))
if m.QueryTimeSec != nil {
        s.Write(cborInteger(int(*m.QueryTimeSec)))
} else {
        s.Write(cborInteger(0))
}

Here, if we don't have the time of the DNS message, we write the dummy value 0 (because earliest-time is not optional in the C-DNS format). One possible evolution of the C-DNS draft could be to have profiles of C-DNS, for instance a "Full" profile where everything is mandatory and a "Partial" one where missing values are allowed.

The C-DNS specification is currently ambiguous about empty arrays. Most arrays are declared, in the grammar, as allowing to be empty. It raises some issues with the current tools (the inspector crashed when meeting these empty arrays, something that Jim Hague fixed during the hackathon). But it is also unclear in its semantics. For instance, the draft says that a value zero for an index mean "not found" but it does not appear to be supported by the current tools, forcing us to invent dummy data.

dnstap formats a part of the DNS messages but not everything. To get the rest, you need to parse the binary blob sent by dnstap. We use the excellent Go DNS package):

import (
  ...
  "github.com/miekg/dns"
...  
msg = new(dns.Msg)
err := msg.Unpack(m.QueryMessage)

This allows us to get information like the QNAME (name used in the question):

qname := make([]byte, 256)
...
n, err = dns.PackDomainName(msg.Question[0].Name, qname, 0, nil, false)
...
s.Write(cborByteString(qname[0:n]))

(Unpack leaves us with a domain name in the presentation - text - format, but C-DNS requires the label - binary - format, which we do with PackDomainName.) Domain names are defined as CBOR byte string and not strings 1) because you cannot be sure of their encoding (strings have to be UTF-8) 2) and for consistency reasons because the same tables can store other things than domain names.

OK, now, we have everything, compile again and run (there is a Unbound running in another window):

go build dnstap/main.go
./main -c -u /var/run/unbound/dnstap.sock > test.cdns
../compactor/inspector   test.cdns  

And the inspector produced a pcap file, as well as a report. Let's try the pcap:

% tcpdump -n -r test.cdns.pcap
reading from file test.cdns.pcap, link-type EN10MB (Ethernet)
13:02:02.000000 IP6 ::1.51834 > ::.0: UDP, length 25
13:02:02.000000 IP 0.0.0.0.0 > 193.0.14.129.53: 666 SOA? 142.com. (25)
13:02:02.000000 IP 193.0.14.129.53 > 0.0.0.0.0: 666- [0q] 0/0/0 (12)
13:02:02.000000 IP6 ::.0 > 2001:503:83eb::30.53: 666 AAAA? ns2.bodis.com. (31)
13:02:02.000000 IP 0.0.0.0.0 > 192.41.162.30.53: 666 AAAA? ns1.bodis.com. (31)
13:02:02.000000 IP6 2001:503:83eb::30.53 > ::.0: 666- [0q] 0/0/0 (12)

You immediately note the missing data. Most of the times, it was the lack of this data in dnstap (the IP addresses, for instance), sometimes it was my own lazyness (the microseconds in the time).

Sometimes, there are things that are available in dnstap but C-DNS offers no way to store them. This is the case with the bailiwick (the zone from which the DNS responses came) or with the fact thata the resolver cache experienced a hit or a miss.

This was the end of the IETF 99 hackathon. The code is available. An example C-DNS file produced by my converter is attached here. Other things would of course be possible:

  • The biggest limit of the current dnstap-to-C-DNS tool is that it uses one C-DNS block per DNS message. This is simpler for the lazy programmer, but it defeats the most important requirments of C-DNS: compression. Compression is done py putting in tables information which is common to several DNS messages. This is the obvious next step.
  • Test with an authoritative name server like Knot (which has dnstap support).

Thanks to Jim Hague for working with me throughout the hackathon (and fixing many things, and answering many questions) and to Sara Dickinson.


L'article seul

RFC 8201: Path MTU Discovery for IP version 6

Date de publication du RFC : Juillet 2017
Auteur(s) du RFC : J. McCann (Digital Equipment Corporation), S. Deering (Retired), J. Mogul (Digital Equipment Corporation), R. Hinden (Check Point Software)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 16 juillet 2017


Ce RFC est l'adaptation à IPv6 du protocole décrit dans le RFC 1191, protocole qui permet de découvrir la MTU du chemin entre deux machines reliées par Internet. Il remplace le RFC 1981.

Avec IPv4, déterminer la MTU maximale du chemin est très utile pour optimiser les performances. Mais elle devient presque indispensable en IPv6, où les routeurs n'ont pas le droit de fragmenter les paquets trop gros (toute fragmentation doit être faite dans la machine de départ). PMTU fait donc quasi-obligatoirement partie d'IPv6. Une alternative est de n'envoyer que des paquets suffisamment petits pour passer partout. C'est moins efficace (on ne tirera pas profit des MTU plus importantes), mais ça peut être intéressant pour des mises en œuvre légères d'IPv6, par exemple pour des objets connectés. (La section 4 du RFC précise en effet que mettre en œuvre cette découverte de la MTU du chemin n'est pas obligatoire.)

L'algorithme (section 3 de notre RFC) est le même qu'en v4, envoyer des paquets, et voir si on reçoit des paquets ICMP Packet too big (RFC 4443, section 3.2) qui contiennent en général la MTU maximale du lien suivant. (Pas de bit DF - Don't fragment - en IPv6 puisque la fragmentation n'est possible qu'à la source. L'annexe A détaille les différences avec l'algorithme IPv4 décrit dans le RFC 1191.)

Voici par exemple un de ces paquets ICMP, vu par tcpdump (déclenché par une commande ping -s 1500 …) :

18:18:44.813927 IP6 2a02:8400:0:3::a > 2605:4500:2:245b::bad:dcaf: ICMP6, packet too big, mtu 1450, length 1240

Il ne faut pas s'arrêter au premier paquet Packet too big reçu, car il peut y avoir des liens réseau avec une MTU encore plus basse plus loin. En outre, la MTU du chemin change avec le temps et il faut donc rester vigilant, on peut recevoir des Packet too big si le trafic passe désormais par un lien de MTU plus faible que la précédente MTU du chemin. Plus délicat, il faut aussi de temps en temps réessayer avec des paquets plus gros, pour voir si la MTU du chemin n'aurait pas augmenté. Comme cela va faire perdre des paquets, s'il n'y a pas eu d'augmentation, il ne faut pas le faire trop souvent. (Dix minutes, dit la section 5.)

La section 4 du RFC précise les exigences qu'il normalise. (Je donne quelques exemples de leur mise en œuvre dans le noyau Linux, dans net/ipv6/route.c.) Par exemple, le nœud IPv6 doit vérifier le contenu des paquets ICMP Packet Too Big qu'il reçoit (le Message Body, cf. RFC 4443, où le routeur émetteur est censé placer des données du paquet original), pour éviter de croire des faux messages ICMP envoyés par un attaquant (rien n'authentifie un paquet ICMP, à part son contenu).

Nouveauté de ce RFC, par rapport à son prédecesseur, un nœud IPv6 doit ignorer les messages Packet Too Big indiquant une MTU inférieur aux 1 280 octets minimum d'IPv6 (le RFC 8021 explique pourquoi). C'est le but de l'instruction mtu = max_t(u32, mtu, IPV6_MIN_MTU); dans le code du noyau Linux, qui ne garde pas la MTU indiquée si elle est inférieur au minimum.

Et la machine IPv6 ne doit jamais augmenter la MTU du chemin suite à la réception d'un Packet Too Big indiquant une MTU supérieure à l'actuelle. Un tel message ne peut être qu'une erreur ou une attaque. Voir les commentaires au début de la fonction rt6_mtu_change_route dans le code Linux.

Comme avec IPv4, l'algorithme PMTU marche mal en pratique car beaucoup de sites filtrent stupidement tous les paquets ICMP et la machine qui tente de faire de la Path MTU discovery n'aura jamais de réponse. IPv6, comme IPv4, devra donc plutôt utiliser la technique du RFC 4821.

La section 5 du RFC rassemble diverses questions de mise en œuvre de cette découverte de la MTU du chemin. D'abord, il y a l'interaction entre IP (couche 3) et les protocoles au dessus, comme TCP mais aussi des protocoles utilisant UDP, comme le DNS. Ces protocoles de transport ont une MMS_S (maximum send transport-message size, voir le RFC 1122) qui est à l'origine la MTU moins la taille des en-têtes. Les protocoles de transport peuvent diminuer cette MMS_S pour ne pas avoir à faire de fragmentation.

Pour le DNS, par exemple, on peut diminuer l'usage de la découverte de la MTU du chemin en diminuant la taille EDNS. Par exemple, avec le serveur nsd, on mettrait :

ipv6-edns-size: 1460

Et, avec 1 460 octets possibles, on serait alors raisonnablement sûr de ne pas avoir de fragmentation donc, même si la découverte de la MTU du chemin marche mal, tout ira bien.

Une fois la découverte de la MTU du chemin faite, où faut-il garder l'information, afin d'éviter de recommencer cette découverte à chaque fois ? Idéalement, cette information est valable pour un chemin donné : si le chemin change (ou, a fortiori, si la destination change), il faudra découvrir à nouveau. Mais la machine finale ne connait pas en général tout le chemin suivi (on ne fait pas un traceroute à chaque connexion). Le RFC suggère donc (section 5.2) de stocker une MTU du chemin par couple {destination, interface}. Si on n'a qu'une seule interface réseau, ce qui est le cas de la plupart des machines terminales, on stocke la MTU du chemin par destination et on maintient donc un état pour chaque destination (même quand on ne lui parle qu'en UDP, qui n'a normalement pas d'état). Les deux exemples suivants concernent une destination où la MTU du chemin est de 1 450 octets. Pour afficher cette information sur Linux pour une adresse de destination :

% ip -6  route get 2a02:8428:46c:5801::4
2a02:8428:46c:5801::4 from :: via 2001:67c:1348:7::1 dev eth0 src 2001:67c:1348:7::86:133 metric 0 
    cache  expires 366sec mtu 1450 pref medium

S'il n'y a pas de PMTU indiquée, c'est que le cache a expiré, réessayez (en UDP, car TCP se fie à la MSS). Notez que, tant qu'on n'a pas reçu un seul Packet Too Big, il n'y a pas de MTU affichée. Donc, pour la plupart des destinations (celles joignables avec comme PMTU la MTU d'Ethernet), vous ne verrez rien. Sur FreeBSD, cet affichage peut se faire avec :

% sysctl -o net.inet.tcp.hostcache.list | fgrep 2a02:8428:46c:5801::4
2a02:8428:46c:5801::4  1450        0     23ms     34ms         0  4388        0 0   23    4 3600

Pour d'autres systèmes, je vous recommande cet article.

Notez qu'il existe des cas compliqués (ECMP) où cette approche de stockage de la PMTU par destination peut être insuffisante. Ce n'est donc qu'une suggestion, chaque implémentation d'IPv6 peut choisir sa façon de mémoriser les MTU des chemins.

Si on veut simplifier la mise en œuvre, et la consommation mémoire, on peut ne mémoriser qu'une seule MTU du chemin, la MTU minimale de tous les chemins testés. Ce n'est pas optimal, mais ça marchera.

Si on stocke la MTU du chemin, comme l'Internet peut changer, il faut se préparer à ce que l'information stockée devienne obsolète. Si elle est trop grande, on recevra des Packet too big (il ne faut donc pas s'attendre à n'en recevoir que pendant la découverte initiale). Si elle est trop petite, on va envoyer des paquets plus petits que ce qu'on pourrait se permettre. Le RFC suggère donc (section 5.3) de ne garder l'information sur la MTU du chemin que pendant une dizaine de minutes.

Le RFC demande enfin (section 5.5) que les mises en œuvre d'IPv6 permettent à l'ingénieur système de configurer la découverte de la MTU du chemin : la débrayer sur certaines interfaces, fixer à la main la MTU d'un chemin, etc.

Il ne faut pas oublier les problèmes de sécurité (section 6 du RFC) : les Packet Too Big ne sont pas authentifiés et un attaquant peut donc en générer pour transmettre une fausse information. Si la MTU indiquée est plus grande que la réalité, les paquets seront jetés. Si elle est plus petite, on aura des sous-performances.

Autre problème de sécurité, le risque que certains administrateurs réseau incompétents ne filtrent tous les messages ICMP, donc également les Packet Too Big (c'est stupide, mais cela arrive). Dans ce cas, la découverte de la MTU du chemin ne fonctionnera pas (cf. RFC 4890 sur les recommandations concernant le filtrage ICMP). L'émetteur devra alors se rabattre sur des techniques comme celle du RFC 4821.

L'annexe B de notre RFC résume les changements faits depuis le RFC 1981. Rien de radical, les principaux portent sur la sécurité (ce sont aussi ceux qui ont engendré le plus de discussions à l'IETF) :

  • Bien expliquer les problèmes créés par le blocage des messages ICMP,
  • Formulations plus strictes sur la validation des messages ICMP,
  • Et surtout, suppression des « fragments atomiques » (RFC 8021).

Le texte a également subi une actualisation générale, les références (pré-)historiques à BSD 4.2 ou bien à TP4 ont été supprimées.

Enfin, pour faire joli, un exemple de traceroute avec recherche de la MTU du chemin activée (option --mtu). Notez la réception d'un Packet Too Big à l'étape 12, annonçant une MTU de 1 450 octets :

% sudo traceroute -6 --mtu --udp --port=53 eu.org
traceroute to eu.org (2a02:8428:46c:5801::4), 30 hops max, 65000 byte packets
 ...
 3  vl387-te2-6-paris1-rtr-021.noc.renater.fr (2001:660:300c:1002:0:131:0:2200)  2.605 ms  2.654 ms  2.507 ms
 4  2001:660:7903:6000:1::4 (2001:660:7903:6000:1::4)  5.002 ms  4.484 ms  5.494 ms
 5  neuf-telecom.sfinx.tm.fr (2001:7f8:4e:2::178)  4.193 ms  3.682 ms  3.763 ms
 6  neuf-telecom.sfinx.tm.fr (2001:7f8:4e:2::178)  14.264 ms  11.927 ms  11.509 ms
 7  2a02-8400-0000-0003-0000-0000-0000-1006.rev.sfr.net (2a02:8400:0:3::1006)  12.760 ms  10.446 ms  11.902 ms
 8  2a02-8400-0000-0003-0000-0000-0000-1c2e.rev.sfr.net (2a02:8400:0:3::1c2e)  10.524 ms  11.477 ms  11.896 ms
 9  2a02-8400-0000-0003-0000-0000-0000-1c2e.rev.sfr.net (2a02:8400:0:3::1c2e)  13.061 ms  11.589 ms  11.915 ms
10  2a02-8400-0000-0003-0000-0000-0000-117e.rev.sfr.net (2a02:8400:0:3::117e)  10.446 ms  10.420 ms  10.361 ms
11  2a02-8400-0000-0003-0000-0000-0000-000a.rev.sfr.net (2a02:8400:0:3::a)  10.460 ms  10.453 ms  10.517 ms
12  2a02-8428-046c-5801-0000-0000-0000-00ff.rev.sfr.net (2a02:8428:46c:5801::ff)  12.495 ms F=1450  12.102 ms  11.203 ms
13  2a02-8428-046c-5801-0000-0000-0000-0004.rev.sfr.net (2a02:8428:46c:5801::4)  12.397 ms  12.217 ms  12.507 ms

La question de la fragmentation en IPv6 a suscité d'innombrables articles. Vous pouvez commencer par celui de Geoff Huston.

Merci à Pierre Beyssac pour avoir fourni le serveur de test configuré comme il fallait, et à Laurent Frigault pour la solution sur FreeBSD.


Téléchargez le RFC 8201


L'article seul

RFC 8200: Internet Protocol, Version 6 (IPv6) Specification

Date de publication du RFC : Juillet 2017
Auteur(s) du RFC : S. Deering (Retired), R. Hinden (Check Point Software)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 16 juillet 2017


Ce RFC est la nouvelle norme du protocole IPv6. IP est le protocole de base de l'Internet, la version 6 y est minoritaire mais est bien plus répandue qu'elle ne l'était lors de la sortie de la précédente norme, qui était le RFC 2460, norme que notre nouveau RFC remplace (et cela fait passer IPv6 au statut de norme Internet, la précédente étant officiellement une proposition de norme).

Pas de changements cruciaux, la norme est nouvelle, mais le protocole reste largement le même. Ce RFC 8200 continue à présenter IPv6 comme « a new version of the Internet Protocol (IP) ». Comme la première norme IPv6 est sortie en 1995, l'adjectif « new » n'est vraiment pas sérieux. Comme, malheureusement, la plupart des formations réseau ne parlent que d'IPv4 et traitent IPv6 de manière bâclée à la fin, le RFC présente IPv6 en parlant de ses différences par rapport à IPv4 (section 1 du RFC) :

  • La principale est évidemment l'espace d'adressage bien plus grand. Alors qu'IPv4, avec ses adresses sur 32 bits, ne peut même pas donner une adresse IP à chaque habitant de la planète (ce qui serait déjà insuffisant), IPv6, avec ses 128 bits d'adresse, permet de distribuer une quantité d'adresses que le cerveau humain a du mal à se représenter. Cette abondance est la principale raison pour laquelle il est crucial de migrer vers IPv6. Les autres raisons sont plus discutables.
  • IPv6 a sérieusement changé le format des options. En IPv4, les options IP étaient un champ de longueur variable dans l'en-tête, pas exactement ce qui est le plus facile à analyser pour un routeur. Le RFC dit qu'IPv6 a simplifié le format mais c'est contestable : une complexité a succédé à une autre. Désormais, le premier en-tête est de taille fixe, mais il peut y avoir un nombre quelconque d'en-têtes chaînés. Le RFC utilise malheureusement des termes publicitaires assez déconnectés de la réalité, en parlant de format plus efficace et plus souple.
  • IPv6 a un mécanisme standard pour étiqueter les paquets appartenant à un même flot de données. Mais le RFC oublie de dire qu'il semble inutilisé en pratique.
  • Et le RFC termine cette énumération par le plus gros pipeautage, prétendre qu'IPv6 aurait de meilleurs capacités d'authentification et de confidentialité. (C'est faux, elles sont les mêmes qu'en IPv4, et peu déployées, en pratique.)

Notez que ce RFC 8200 ne spécifie que le format des paquets IPv6. D'autres points très importants sont normalisés dans d'autres RFC, les adresses dans le RFC 4291, et ICMP dans le RFC 4443.

La section 3 présente le format des paquets IPv6 :

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version| Traffic Class |           Flow Label                  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Payload Length        |  Next Header  |   Hop Limit   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                         Source Address                        +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                      Destination Address                      +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    

(D'ailleurs, si quelqu'un sait pourquoi l'adresse IP source est avant la destination ? Il me semblerait plus logique que ce soit l'inverse, puisque tous les routeurs sur le trajet doivent examiner l'adresse destination, alors qu'on n'a pas toujours besoin de l'adresse source.) Le numéro de version vaut évidemment 6, le « traffic class » est présenté en section 7, le « flow label » en section 6.

Le champ « Next header » remplace le « Protocol » d'IPv4. Il peut indiquer le protocole de transport utilisé (la liste figure dans un registre IANA et est la même pour IPv4 et IPv6) mais aussi les en-têtes d'extension, une nouveauté d'IPv6, présentée en section 4 de notre RFC.

Le champ « Hop limit » remplace le « Time to Live » d'IPv4. En fait, les deux ont exactement la même sémantique, qui est celle d'un nombre maximal de routeurs utilisés sur le trajet (le but est d'éviter les boucles infinies dans le réseau). Autrefois, dans IPv4, il était prévu que ce champ soit réellement une durée, mais aucune mise en œuvre d'IPv4 ne l'avait jamais utilisé comme ceci. Le renommage dans IPv6 fait donc correspondre la terminologie avec une réalité ancienne (cf. aussi la section 8.2). Notez que c'est ce champ qui est utilisé par traceroute.

Voici une simple connexion HTTP en IPv6, vue avec tcpdump et tshark. Le client a demandé /robots.txt et a obtenu une réponse négative (404). Si vous voulez, le pcap complet est ipv6-http-connection.pcap. Voici d'abord avec tcpdump avec ses options par défaut :

15:46:21.768536 IP6 2001:4b98:dc2:43:216:3eff:fea9:41a.37703 > 2605:4500:2:245b::42.80: Flags [S], seq 3053097848, win 28800, options [mss 1440,sackOK,TS val 1963872568 ecr 0,nop,wscale 7], length 0
    

On voit les deux adresses IPv6, tcpdump n'affiche rien d'autre de l'en-tête de couche 3, tout le reste est du TCP, le protocole de transport utilisé par HTTP. Avec l'option -v de tcpdump :

15:46:21.768536 IP6 (hlim 64, next-header TCP (6) payload length: 40) 2001:4b98:dc2:43:216:3eff:fea9:41a.37703 > 2605:4500:2:245b::42.80: Flags [S] [...]
    

Cette fois, on voit le « Hop limit » (64), l'en-tête suivant (TCP, pas d'en-tête d'extension) et la longueur (40 octets). Pour avoir davantage, il faut passer à tshark (décodage complet en ipv6-http-connection.txt) :

Internet Protocol Version 6, Src: 2001:4b98:dc2:43:216:3eff:fea9:41a, Dst: 2605:4500:2:245b::42
    0110 .... = Version: 6
    .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00 (DSCP: CS0, ECN: Not-ECT)
        .... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
        .... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    .... .... .... 0000 0000 0000 0000 0000 = Flow label: 0x00000
    Payload length: 40
    Next header: TCP (6)
    Hop limit: 64
    Source: 2001:4b98:dc2:43:216:3eff:fea9:41a
    [Source SA MAC: Xensourc_a9:04:1a (00:16:3e:a9:04:1a)]
    Destination: 2605:4500:2:245b::42
    [Source GeoIP: France]
        [Source GeoIP Country: France]
    [Destination GeoIP: United States]
        [Destination GeoIP Country: United States]
    

On a cette fois tout l'en-tête IPv6 : notons le « Flow Label » (j'avais bien dit que peu de gens s'en servaient, il est nul dans ce cas).

La section 4 de notre RFC est dédiée à une nouveauté d'IPv6, les en-têtes d'extension. Au lieu d'un champ « Options » de taille variable (donc difficile à analyser) comme en IPv4, IPv6 met les options IP dans des en-têtes supplémentaires, chaînés avec l'en-tête principal. Par exemple, si un paquet UDP a un en-tête « Destination Options », le champ « Next header » de l'en-tête principal vaudra 60 (pour « Destination Options »), il sera suivi de l'en-tête d'extension « Destination Options » qui aura, lui, un « Next header » de 17 pour indiquer que ce qui suit est de l'UDP. (Je rappelle que les valeurs possibles pour « Next Header » sont dans un registre IANA.) Il peut y avoir zéro, un ou davantage d'en-têtes d'extension chaînés entre eux.

Notez qu'analyser cette chaîne d'en-têtes est compliqué car les en-têtes n'ont pas tous le même format (le RFC 6564 a créé un format unique, mais cela ne concerne que les futurs en-têtes.) Il est donc exagéré de dire que la suppression du champ « Options » de taille variable a simplifié les choses.

Les différents en-têtes ne sont pas tous traités pareillement par les routeurs. Il existe notamment un en-tête, « Hop-by-hop Options » qui doit être examiné par tous les routeurs du trajet (cette obligation, jamais respectée, a de toute façon été relâchée par rapport au RFC 2460). C'est pour cela qu'il doit être placé au début des en-têtes, juste après l'en-tête principal. Les autres en-têtes d'extension doivent être ignorés par les routeurs.

Comme il est compliqué de rajouter un nouveau modèle d'en-tête (il faudrait modifier toutes les machines IPv6), une solution légère existe pour les options simples : utiliser les en-têtes d'options, « Hop-by-hop Options » et « Destination Options ». Tous les deux sont composés d'une série d'options encodées en TLV. En outre, le type de l'option indique, dans ses deux premiers bits, le traitement à appliquer au paquet si le système ne connait pas cette option. Si les deux premiers bits sont à zéro, on ignore l'option et on continue. Autrement, on jette le paquet (les trois valeurs restantes, 01, 10 et 11, indiquent si on envoie un message d'erreur ICMP et lequel). Ainsi, l'option pour la destination de numéro 0x07 (utilisée par le protocole de sécurité du RFC 5570) est facultative : elle a les deux premiers bits à zéro et sera donc ignorée silencieusement par les destinataires qui ne la connaissent pas (cf. registre IANA.)

« Destination Options », comme son nom l'indique, n'est examinée que par la machine de destination. Si vous voulez envoyer des paquets avec cet en-tête, regardez mon article.

Outre les en-têtes « Hop-by-hop Options » et « Destination Options », il existe des en-têtes :

  • « Routing Header », qui permet de spécifier le routage depuis la source (un peu comme les options de « source routing » en IPv4). Il y a plusieurs types, et les valeurs possibles sont dans un registre IANA, géré suivant les principes du RFC 5871. (Le type 0 a été retiré depuis le RFC 5095.)
  • « Fragment Header », qui permet de fragmenter les paquets de taille supérieure à la MTU.
  • Les autres en-têtes sont ceux utilisés par IPsec, décrits dans les RFC 4302 et RFC 4303.

La fragmentation est différente de celle d'IPv4. En IPv6, seule la machine émettrice peut fragmenter, pas les routeurs intermédiaires. Si le paquet est plus grand que la MTU, on le découpe en fragments, chaque fragment portant un « Fragment Header ». Cet en-tête porte une identification (un nombre sur 32 bits qui doit être unique parmi les paquets qui sont encore dans le réseau), un décalage (offset) qui indique à combien d'octets depuis le début du paquet original se situe ce fragment (il vaut donc zéro pour le premier fragment) et un bit qui indique si ce fragment est le dernier. À la destination, le paquet est réassemblé à partir des fragments. (Il est désormais interdit que ces fragments se recouvrent, cf. RFC 5722.) Voici un exemple de fragmentation. La sonde Atlas n° 6271 a interrogé un serveur DNS de la racine Yeti avec le type de question ANY qui signifie « envoie-moi tout ce que tu peux / veux ». La réponse, plus grande que la MTU (plus de quatre kilo-octets !), a été fragmentée en trois paquets (le pcap complet est en ipv6-dns-frag.pcap) :

16:14:27.112945 IP6 2001:67c:217c:4::2.60115 > 2001:4b98:dc2:45:216:3eff:fe4b:8c5b.53: 19997+ [1au] ANY? . (28)
16:14:27.113171 IP6 2001:4b98:dc2:45:216:3eff:fe4b:8c5b > 2001:67c:217c:4::2: frag (0|1232) 53 > 60115: 19997*- 35/0/1 NS bii.dns-lab.net., NS yeti.bofh.priv.at., NS yeti.ipv6.ernet.in., NS yeti.aquaray.com., NS yeti.mind-dns.nl., NS dahu1.yeti.eu.org., NS dahu2.yeti.eu.org., NS yeti1.ipv6.ernet.in., NS ns-yeti.bondis.org., NS yeti-ns.ix.ru., NS yeti-ns.lab.nic.cl., NS yeti-ns.tisf.net., NS yeti-ns.wide.ad.jp., NS yeti-ns.conit.co., NS yeti-ns.datev.net., NS yeti-ns.switch.ch., NS yeti-ns.as59715.net., NS yeti-ns1.dns-lab.net., NS yeti-ns2.dns-lab.net., NS yeti-ns3.dns-lab.net., NS xn--r2bi1c.xn--h2bv6c0a.xn--h2brj9c., NS yeti-dns01.dnsworkshop.org., NS yeti-dns02.dnsworkshop.org., NS 3f79bb7b435b05321651daefd374cd.yeti-dns.net., NS ca978112ca1bbdcafac231b39a23dc.yeti-dns.net., RRSIG, NSEC, RRSIG[|domain]
16:14:27.113187 IP6 2001:4b98:dc2:45:216:3eff:fe4b:8c5b > 2001:67c:217c:4::2: frag (1232|1232)
16:14:27.113189 IP6 2001:4b98:dc2:45:216:3eff:fe4b:8c5b > 2001:67c:217c:4::2: frag (2464|637)
    

On note que tcpdump n'a interprété qu'un seul fragment comme étant du DNS, la premier, puisque c'était le seul qui portait l'en-tête UDP, avec le numéro de port 53 identifiant du DNS. Dans le résultat de tcpdump, après le mot-clé frag, on voit le décalage du fragment par rapport au début du paquet original (respectivement 0, 1232 et 2464 pour les trois fragments), et la taille du fragment (respectivement 1232, 1232 et 637 octets). Vu par tshark (l'analyse complète est en ipv6-dns-frag.txt), le premier fragment contient :

    
    Type: IPv6 (0x86dd)
Internet Protocol Version 6, Src: 2001:4b98:dc2:45:216:3eff:fe4b:8c5b, Dst: 2001:67c:217c:4::2
    Payload length: 1240
    Next header: Fragment Header for IPv6 (44)
    Fragment Header for IPv6
        Next header: UDP (17)
        Reserved octet: 0x00
        0000 0000 0000 0... = Offset: 0 (0 bytes)
        .... .... .... .00. = Reserved bits: 0
        .... .... .... ...1 = More Fragments: Yes
        Identification: 0xcbf66a8a
Data (1232 bytes)

On note le « Next Header » qui indique qu'un en-tête d'extension, l'en-tête « Fragmentation », suit l'en-tête principal. Le bit M (« More Fragments ») est à 1 (ce n'est que le premier fragment, d'autres suivent), le décalage (« offset ») est bien sûr de zéro. L'identificateur du paquet est de 0xcbf66a8a. Le dernier fragment, lui, contient :

Internet Protocol Version 6, Src: 2001:4b98:dc2:45:216:3eff:fe4b:8c5b, Dst: 2001:67c:217c:4::2
    Payload length: 645
    Next header: Fragment Header for IPv6 (44)
    Fragment Header for IPv6
        Next header: UDP (17)
        Reserved octet: 0x00
        0000 1001 1010 0... = Offset: 308 (2464 bytes)
        .... .... .... .00. = Reserved bits: 0
        .... .... .... ...0 = More Fragments: No
        Identification: 0xcbf66a8a

Cette fois, le « Next Header » indique que c'est de l'UDP qui suit, le décalage est de 2464, et le bit M est à zéro (plus d'autres fragments). L'identificateur est le même, c'est celui du paquet original, et c'est grâce à lui que la machine de destination saura réassembler les fragments. Notez qu'à la fin de l'analyse par tshark figure un réassemblage complet du paquet, ce qui permet une analyse DNS complète.

Et si je ne suis pas satisfait des en-têtes d'extension existants et que je veux créer le mien ? C'est en général une mauvaise idée. La plupart des cas concrets devraient être résolus avec un des en-têtes déjà normalisés. Notamment, l'en-tête « Destination Options » est là pour la majorité des situations. C'est lui qu'il faut regarder en premier (ce qu'ont fait les RFC 6744, RFC 6788 ou RFC 7837, la liste complète figurant dans un registre IANA). Notre RFC 8200 exige donc que, si vous tenez à créer un nouvel en-tête, vous expliquiez bien pourquoi c'est indispensable, et pourquoi aucun des en-têtes existants ne convient.

Il est également déconseillé de créer de nouvelles options « hop-by-hop » (la liste actuelle est à l'IANA), car ces options doivent être traitées par tous les routeurs du trajet. Il y a un risque sérieux qu'ils laissent tomber les paquets ayant cet en-tête « Hop-by-hop Options », ou bien qu'ils le traitent plus lentement (logiciel du routeur et pas circuits matériels spécialisés). Là aussi, il faudra donc une justification sérieuse.

La section 5 de notre RFC se penche sur un problème délicat, la taille des paquets. IPv6 exige une MTU d'au moins 1 280 octets, mais les paquets sont souvent plus grands (par exemple 1 500 octets si on part d'un Ethernet). Beaucoup de liens ont, en pratique, une MTU de 1 500 octets mais IPv6 étant souvent porté par des tunnels (en raison de l'immobilisme de beaucoup de FAI, qui ne déploient toujours pas IPv6), un certain nombre de liens offrent moins de 1 500 octets. Normalement, l'émetteur d'un paquet IPv6 doit faire de la découverte de la MTU du chemin (RFC 8201) afin de pouvoir fragmenter, si nécessaire. Une mise en œuvre « paresseuse » d'IPv6 pourrait se dispenser de découverte de la MTU du chemin, et se limiter à 1 280 octets par paquet.

Le problème est que la découverte de la MTU du chemin dépend du bon fonctionnement d'ICMP. L'émetteur d'un paquet doit pouvoir recevoir les paquets ICMP « Packet too big » (RFC 4443, section 3.2). Or, un certain nombre de pare-feux, stupidement configurés par des amateurs, bloquent tout ICMP « pour des raisons de sécurité » (c'est d'ailleurs une bonne question pour les entretiens d'embauche d'un administrateur de réseaux : « filtrer ICMP en entrée ou pas ? » S'il répond Oui, on est sûr qu'il est incompétent.) Ne recevant pas les « Packet too big », l'émetteur risque de croire à tort que ses paquets sont passés. En outre, si l'émetteur décide de fragmenter (en général, quand la MTU du chemin est inférieure à la sienne, avec TCP, on réduit la MSS, avec UDP, on fragmente), il faut que les fragments passent à travers le réseau. Et, là encore, un certain nombre de pare-feux bêtement configurés bloquent les fragments. Donc, en pratique, découverte du chemin + fragmentation est un processus fragile, en raison de ce véritable sabotage par des middleboxes.

C'est certainement le plus gros problème pratique lors du déploiement d'IPv6. On peut même penser à prendre des mesures radicales et coûteuses, comme d'abaisser la MTU à 1 280 octets pour être tranquille. Moins violent, il est fréquent de voir des MTU à 1 480 octets. Voici par exemple la configuration de mon routeur Turris Omnia pour passer par l'IPv6 de Free (un tunnel) :

config interface 'lan'
	option ip6assign '64'
	option ip6addr '2001:db8:42::1:fe/64'
	option ip6prefix '2001:db8:42::/64'
	option ip6gw '2001:db8:42::1'
	option mtu '1480'
    

Et le « flow label », dont j'avais parlé plus haut ? Il est décrit dans la (très courte) section 6, qui renvoie surtout au RFC 6437. En pratique, ce champ semble peu utilisé comme on l'a vu dans l'exemple décodé par tshark.

Même chose pour le « traffic class », en section 7 : pour son utilisation pour la différenciation de trafic, voir les RFC 2474 et RFC 3168.

Maintenant qu'IPv6, protocole de couche 3, a été bien défini, le RFC monte vers la couche 4, en consacrant sa section 8 aux problèmes des couches supérieures. Cela concerne notamment la somme de contrôle. Si vous avez fait attention au schéma de l'en-tête IPv6 de la section 3, vous avez noté qu'il n'y avait pas de champ « Header Checksum », contrairement à ce qui existait en IPv4. En IPv6, pas de somme de contrôle en couche 3, c'était une tâche supplémentaire pour les routeurs (pour citer le RFC 791, « Since some header fields change (e.g., time to live), this is recomputed and verified at each point that the internet header is processed. »), tâche dont ils sont désormais dispensés.

Par contre, la somme de contrôle existe pour les en-têtes de couche 4 et elle devient même obligatoire pour UDP (elle était facultative en IPv4, quoique très fortement recommandée). (Voir le RFC 1071, au sujet de cette somme de contrôle.)

Un gros changement de ce RFC par rapport à son prédécesseur, le RFC 2460, concerne la sécurité. La section sur la sécurité est passée d'une annexe de deux lignes (qui se contentait de passer le bébé à IPsec) à une analyse plus détaillée (section 10 du RFC). La question est délicate car la sécurité d'IPv6 a souvent fait l'objet de FUD, visant à justifier l'immobilisme de pas mal d'acteurs. Il est évidemment plus valorisant de dire « nous ne migrons pas vers IPv6 pour des raisons de sécurité » que de reconnaitre « nous sommes trop flemmards et paralysés par la peur du changement ». (Cf. mon exposé sur la sécurité d'IPv6 à l'ESGI.) S'il fallait synthétiser (la deuxième partie de cette synthèse ne figure pas dans le RFC), je dirais :

  • La sécurité d'IPv6 est quasiment la même qu'en IPv4 (ce qui est logique, il ne s'agit après tout que de deux versions du même protocole). Les grandes questions de sécurité d'IPv4 (usurpation d'adresse source, relative facilité à faire des attaques par déni de service, pas d'authentification ou de confidentialité par défaut) sont exactement les mêmes en IPv6. (C'est une blague courante à l'IETF de dire que si IPv4, l'un des plus grands succès de l'ingéniérie du vingtième siècle, était présenté à l'IESG aujourd'hui, il serait rejeté pour sa trop faible sécurité.)
  • Par contre, en pratique, les solutions techniques d'attaque et de défense, ainsi que les compétences des attaquants et des défenseurs, sont bien plus faibles en IPv6. Pas mal de logiciels « de sécurité » ne gèrent pas IPv6, bien des logiciels de piratage ne fonctionnent qu'en IPv4, les administrateurs système sont déroutés face à une attaque IPv6, et les pirates ne pensent pas à l'utiliser. (Faites l'expérience : configurez un pot de miel SSH en IPv4 et IPv6. Vous aurez plusieurs connexions par jour en IPv4 et jamais une seule en IPv6.) L'un dans l'autre, je pense que ces deux aspects s'équilibrent.

Bon, assez de stratégie, passons maintenant aux problèmes concrets que décrit cette section 10. Elle rappelle des risques de sécurité qui sont exactement les mêmes qu'en IPv4 mais qu'il est bon de garder en tête, ce sont des problèmes fondamentaux d'IP :

  • Communication en clair par défaut, donc espionnage trop facile pour les États, les entreprises privées, les pirates individuels. (Cf. Snowden.)
  • Possibilité de rejouer des paquets déjà passés, ce qui permet certaines attaques. Dans certains cas, on peut modifier le paquet avant de le rejouer, et ça passera.
  • Possibilité de générer des faux paquets et de les injecter dans le réseau.
  • Attaque de l'homme du milieu : une entité se fait passer pour l'émetteur auprès du vrai destinataire, et pour le destinataire auprès du vrai émetteur.
  • Attaque par déni de service, une des plaies les plus pénibles de l'Internet.

En cohérence avec le RFC 2460 (mais pas avec la réalité du terrain), notre RFC recommande IPsec (RFC 4301) comme solution à la plupart de ces problèmes. Hélas, depuis le temps qu'il existe, ce protocole n'a jamais connu de déploiement significatif sur l'Internet public (il est par contre utilisé dans des réseaux privés, par exemple le VPN qui vous permet de vous connecter avec votre entreprise de l'extérieur utilise probablement une variante plus ou moins standard d'IPsec). Une des raisons de ce faible déploiement est la grande complexité d'IPsec, et la complexité pire encore de ses mises en œuvre. En pratique, même si le RFC ne le reconnait que du bout des lèvres, ce sont les protocoles applicatifs comme SSH ou TLS, qui sécurisent l'Internet.

Pour les attaques par déni de service, par contre, aucune solution n'est proposée : le problème ne peut pas forcément se traiter au niveau du protocole réseau.

La différence la plus spectaculaire entre IPv4 et IPv6 est évidemment la taille des adresses. Elle rend le balayage bien plus complexe (mais pas impossible), ce qui améliore la sécurité (l'Internet IPv4 peut être exploré incroyablement vite, par exemple avec masscan, et, si on est trop flemmard pour balayer soi-même, on peut utiliser des balayages déjà faits, par exemple par Shodan ou https://scans.io/). Le RFC 7707 fait une très bonne synthèse de l'état de l'art en matière de balayage IPv6. Par exemple, Shodan, cité plus haut, doit utiliser des techniques assez douteuses pour récolter des adresses IPv6 à examiner.

Et qu'en est-il de la vie privée ? L'argument, largement FUDé, a été beaucoup utilisé contre IPv6. Le RFC note qu'IPv6 a entre autre pour but de rendre inutile l'usage du NAT, dont certaines personnes prétendent qu'il protège un peu les utilisateurs. L'argument est largement faux : le NAT (qui est une réponse à la pénurie d'adresses IPv4, pas une technique de sécurité) ne protège pas contre tout fingerprinting, loin de là. Et, si on veut empêcher les adresses IP des machines du réseau local d'être visibles à l'extérieur, on peut toujours faire du NAT en IPv6, si on veut, ou bien utiliser des méthodes des couches supérieures (comme un relais).

Autre question de vie privée avec IPv6, les adresses IP fondées sur l'adresse MAC. Cette ancienne technique, trop indiscrète, a été abandonnée avec les RFC 4941 et RFC 7721, le premier étant très déployé. Il est curieux de constater que cet argument soit encore utilisé, alors qu'il a perdu l'essentiel de sa (faible) pertinence.

Mais il y avait bien des problèmes de sécurité concrets avec le précédent RFC 2460, et qui sont réparés par ce nouveau RFC :

  • Les « fragments atomiques » ne doivent désormais plus être générés (RFC 8021) et, si on en reçoit, doivent être « réassemblés » par une procédure spéciale (RFC 6946).
  • Les fragments qui se recouvrent partiellement sont désormais interdits (cf. RFC 5722, le réassemblage des fragments en un paquet est déjà assez compliqué et sujet à erreur comme cela).
  • Si le paquet est fragmenté, les en-têtes d'extension doivent désormais tous être dans le premier fragment (RFC 7112).
  • L'en-tête de routage (Routing Header) de type 0, dit « RH0 », est abandonné, il posait trop de problèmes de sécurité (cf. RFC 5095 et RFC 5871).

L'annexe B de notre RFC résume les changements depuis le RFC 2460. Pas de choses révolutionnaires, les changements les plus importantes portaient sur la sécurité, comme listé un peu plus haut (section 10 du RFC). On notera comme changements :

  • [Section 1] Clarification que IPv6 est gros-boutien, comme IPv4 (cf. l'annexe B du RFC 791).
  • [Section 3] Clarification de l'utilisation du « hop limit », décrémenté de 1 à chaque routeur.
  • [Section 4] Clarification du fait qu'un équipement intermédiaire ne doit pas tripoter les en-têtes d'extension (à part évidemment « Hop-by-hop Options »), ni les modifier, ni en ajouter ou retirer, ni même les lire.
  • [Section 4] Le traitement de l'en-tête « Hop-by-hop Options par les routeurs sur le trajet n'est plus obligatoire. Si un routeur est pressé, il peut s'en passer (le RFC suit ainsi la pratique).
  • [Section 4.4] Suppression de l'en-tête de routage de type 0.
  • [Section 4.5] Pas mal de changements sur la fragmentation (un processus toujours fragile !), notamment l'abandon des fragments atomiques.
  • [Section 4.8] Intégration du format unique des éventuels futurs en-têtes d'extension, format initialement présenté dans le RFC 6564.
  • [Section 6] Reconnaissance du fait que « Flow Label » n'était pas réellement bien défini, et délégation de cette définition au RFC 6437.
  • [Section 8.1] Autorisation, dans certains cas bien délimités, d'omission de la somme de contrôle UDP (RFC 6935).
  • Il y a eu également correction de diverses erreurs comme les 2541 (omission sur la définition du « Flow Label ») et 4279 (paquet entrant avec un « Hop Limit » déjà à zéro).

Le projet de mise à jour de la norme IPv6 avait été lancé en 2015 (voici les supports du premier exposé).

D'habitude, je termine mes articles sur les RFC par des informations sur l'état de mise en œuvre du RFC. Mais, ici, il y en a tellement que je vous renvoie plutôt à cette liste. Notez que des tests d'interopérabilité ont été faits sur les modifications introduites par ce nouveau RFC et que les résultats publiés n'indiquent pas de problème.

Parmi les publications récentes sur le déploiement d'IPv6, signalons :


Téléchargez le RFC 8200


L'article seul

RFC 8212: Default External BGP (EBGP) Route Propagation Behavior without Policies

Date de publication du RFC : Juillet 2017
Auteur(s) du RFC : J. Mauch (Akamai), J. Snijders (NTT), G. Hankins (Nokia)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF grow
Première rédaction de cet article le 10 juillet 2017


Ce RFC est très court, mais concerne un problème fréquent sur l'Internet : les fuites de routes BGP. Traditionnellement, un routeur BGP acceptait par défaut toutes les routes, et annonçait par défaut à ses voisins toutes les routes qu'il connaissait. Il fallait une configuration explicite pour ne pas le faire. En cas d'erreur, ce comportement menait à des fuites (RFC 7908). Notre RFC normalise désormais le comportement inverse : un routeur BGP ne doit, par défaut, rien accepter et rien annoncer. Il faut qu'il soit configuré explicitement si on veut le faire.

Avec l'ancien comportement, la configuration de certains routeurs BGP, les plus imprudents, indiquait les pairs avec qui on échangeait de l'information, plus un certain nombre de routes qu'on n'envoyait pas. Si une erreur faisait qu'on recevait tout à coup des routes imprévues, on les acceptait, et on les renvoyait telles quelles, propageant la fuite (RFC 7908). Des cas fameux de fuites ne manquent pas (voir par exemple celle d'un opérateur malaisien). L'idée derrière ce comportement était d'assurer la connectivité du graphe de l'Internet. Aujourd'hui, on est plutôt plus sensibles aux risques de sécurité qu'à ceux de partitionnement du graphe, et les bonnes pratiques demandent depuis longtemps qu'on indique explicitement ce qu'on accepte et ce qu'on envoie. Voyez par exemple les recommandations de l'ANSSI.

En pratique, ce très court RFC ajoute juste deux paragraphes à la norme BGP, le RFC 4271. Dans sa section 9.1, les paragraphe en question disent désormais qu'il ne doit pas y avoir du tout d'exportation ou d'importation de routes, par défaut. Notez donc que cela ne change pas le protocole BGP, juste le comportement local.

Du moment qu'on change le comportement par défaut, il va y avoir un problème de transition (et ce point a soulevé des discussions à l'IETF). Si le logiciel du routeur s'adaptait au nouveau RFC, certains opérateurs seraient bien surpris que leurs routes ne soient tout coup plus annoncées. L'annexe A du RFC recommande une stratégie en deux temps :

  • D'abord introduire une nouvelle option « BGP non sécurisé » qui serait à Vrai par défaut (mais que les opérateurs pourraient changer), ce qui garderait le comportement actuel mais avec un avertissement émis quelque part « attention, vous exportez des routes sans décision explicite »,
  • Ensuite, dans la version suivante du logiciel, faire que cette option soit à Faux par défaut.

Les routeurs Cisco utilisant IOS-XR ont déjà ce comportement.

Et pour finir sur une note d'humour, à une réunion IETF (IETF 97), le projet qui a finalement mené à ce RFC était illustré… de photos de préservatifs. Pratiquez donc le BGP safe, l'Internet vous remerciera.


Téléchargez le RFC 8212


L'article seul

Articles des différentes années : 2017  2016  2015  2014  2013  2012  2011  Précédentes années

Syndication : en HTTP non sécurisé, Flux Atom avec seulement les résumés et Flux Atom avec tout le contenu, en HTTPS, Flux Atom avec seulement les résumés et Flux Atom avec tout le contenu.