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.


Noms de domaine, DNS et vie privée

Première rédaction de cet article le 14 janvier 2018


Le 13 janvier 2018, à Fontenay-le-Fleury, à l'invitation de l'association de libristes Root66, j'ai fait un exposé sur les noms de domaine, le DNS et la vie privée. Presque trois heures, ouf (mais une galette après).

Voici les transparents de l'exposé :

Désolé, pas de vidéo, ça n'a pas été filmé. Mais vous pouvez lire mes articles sur les RFC 7626 (description du problème), RFC 7858 (chiffrement du DNS) et RFC 7816 (minimisation des données envoyées). Et bien sûr l'excellent portail « DNS privacy ».

Merci beaucoup à Zenzla زنزلا pour la proposition et l'organisation.


L'article seul

RFC 8300: Network Service Header (NSH)

Date de publication du RFC : Janvier 2018
Auteur(s) du RFC : P. Quinn (Cisco), U. Elzur (Intel), C. Pignataro (Cisco)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF sfc
Première rédaction de cet article le 14 janvier 2018


Ce Network Service Header est un mécanisme concret pour faire passer sur le réseau les paquets destinés à une SF (Service Function, voir RFC 7665 pour l'architecture et les définitions). On colle un NSH, stockant plusieurs métadonnées, au paquet à traiter, on encapsule ce paquet à traiter et on l'envoie au dispositif de traitement via un réseau overlay. Et on fait l'opération inverse au retour. L'encapsulation peut se faire dans IP (par exemple avec GRE) ou dans un autre protocole.

Les métadonnées mises dans le NSH sont le résultat d'un processus de classification où le réseau décide ce qu'on va faire au paquet. Par exemple, en cas de dDoS, le classificateur décide de faire passer tous les paquets ayant telle adresse source par un équipement de filtrage plus fin, et met donc cette décision dans le NSH (section 7.1). Le NSH contient les informations nécessaires pour le SFC (Service Function Chain, RFC 7665). Sa lecture est donc très utile pour l'opérateur du réseau (elle contient la liste des traitements choisis, et cette liste peut permettre de déduire des informations sur le trafic en cours) et c'est donc une information plutôt sensible (voir aussi le RFC 8165).

Le NSH ne s'utilise qu'à l'intérieur de votre propre réseau (il n'offre, par défaut, aucune authentification et aucune confidentialité, voir section 8 du RFC). C'est à l'opérateur de prendre les mesures nécessaires, par exemple en chiffrant tout son trafic interne. Cette limitation à un seul domaine permet également de régler le problème de la fragmentation, si ennuyeux dès qu'on encapsule, ajoutant donc des octets. Au sein d'un même réseau, on peut contrôler tous les équipements et donc s'assurer que la MTU sera suffisante, ou, sinon, que la fragmentation se passera bien (section 5 du RFC).

Tout le projet SFC (dont c'est le troisième RFC) repose sur une vision de l'Internet comme étant un ensemble de middleboxes tripotant les paquets au passage, plutôt qu'étant un ensemble de machines terminales se parlant suivant le principe de bout en bout. C'est un point important à noter pour comprendre les débats au sein de l'IETF.


Téléchargez le RFC 8300


L'article seul

Cours BGP au CNAM

Première rédaction de cet article le 10 janvier 2018


Le 9 janvier 2018, c'était la première édition de mon cours BGP de trois heures au CNAM. Pour l'anecdote, c'était dans le bâtiment où il y avait eu la première connexion UUCP/Usenet, et le premier serveur HTTP public, en France.

Voici les supports de l'exposé :

Désolé, pas de vidéo publique.

J'ai fait aussi un cours DNS au même endroit.

Merci à Éric Gressier pour l'idée et l'organisation, et aux élèves pour avoir posé plein de questions pas toujours faciles.


L'article seul

RFC 8289: Controlled Delay Active Queue Management

Date de publication du RFC : Janvier 2018
Auteur(s) du RFC : K. Nichols (Pollere), V. Jacobson, A. McGregor, J. Iyengar (Google)
Expérimental
Réalisé dans le cadre du groupe de travail IETF aqm
Première rédaction de cet article le 6 janvier 2018


Ah, la gestion des files d'attentes… Le cauchemar de plein d'étudiants en informatique. Et cela ne cesse pas quand ils deviennent ingénieurs et qu'il faut construire un routeur pour connecter des réseaux de capacités différentes, et qui aura donc besoin de files d'attente. Bref, dès qu'on n'a pas assez de ressources (et on n'en aura jamais assez), il faut optimiser ses files d'attente. Ce nouveau RFC décrit le mécanisme CoDel (mis en œuvre depuis un certain temps dans le noyau Linux) qui permet notamment de limiter le terrible, l'épouvantable bufferbloat.

L'algorithme naïf pour gérer une file d'attente est le suivant (on prend le cas simple d'un routeur qui n'a que deux interfaces et une seule file d'attente dans chaque direction) : les paquets arrivent au routeur et sont mis dans la file gérée en FIFO. Dès que des ressources suffisantes sont disponibles pour envoyer un paquet (dès que l'interface de sortie est libre), on envoie le paquet et on le retire donc de la file. Si un paquet arrive quand la file est pleine, on le jette : TCP détectera cette perte, réduira son rythme d'envoi, et réémettra les données manquantes.

Avec cet algorithme simpliste, il y a une décision importante à prendre, la taille de la file. Le trafic sur l'Internet est tout sauf constant : des périodes de grand calme succèdent à des pics de trafic impressionnants. Si la file d'attente est trop petite, on ne pourra pas écluser ces pics, et on jettera des paquets. Ça tombe bien, le prix des mémoires baisse, on va pouvoir mettre des files plus grandes, non ? Eh bien non car arrive le fameux bufferbloat. Si la file est trop grande, les paquets y séjourneront très longtemps, et on augmentera ainsi la latence, au grand dam des applications sensibles comme SSH ou les jeux en ligne. Bref, on est coincés, il n'y a pas de solution idéale. Pire, si on ne jette des paquets que lorsque la file est pleine, on risque de tomber dans le cas où l'équilibre se fait avec une file d'attente toujours pleine, et donc avec une mauvaise latence.

Bien sûr, il y a longtemps que les routeurs n'utilisent plus d'algorithme aussi naïf que celui présenté ici. Tout un travail a été fait sur l'AQM : voir par exemple les RFC 7567 et RFC 8033. Mais le problème de la file d'attente toujours pleine est toujours là. Sa première description est dans le RFC 896 en 1984. Plus récemment, on le trouve mentionné dans une présentation de Jim Gettys à l'IETF 80 (« Bufferbloat: Dark Buffers in the Internet ») développé dans un article dans Communications of the ACM (Gettys a beaucoup fait pour la prise de conscience des dangers du bufferbloat.).

Résoudre le problème de l'« obésité du tampon » (bufferbloat) en réduisant la taille des files d'attentes ne serait pas une solution : les tampons sont là pour une bonne raison, pour absorber les problèmes brefs, lorsque, temporairement, on reçoit davantage de paquets que ce que l'on peut transmettre. Le fait que des files plus petites ne sont pas une solution a déjà été exposé dans le RFC 2309, dans « A Rant on Queues » de Van Jacobson, dans le rapport « RED in a Different Light » et dans l'article fondateur de CoDel, « Controlling Queue Delay » (article complet sur Sci-Hub, cherchez 10.1145/2209249.2209264). Le problème n'est pas tant la taille de la file en soi (ce n'est pas la taille qui compte), mais si c'est une « bonne » file ou une « mauvaise » file (au passage, si vous ne connaissez pas la différence entre le bon chasseur et le mauvais chasseur, ne ratez pas l'indispensable sketch des Inconnus).

CoDel est donc une vieille idée. Elle veut répondre, entre autres, aux critères suivants (section 1 du RFC) :

  • Être « zéro-configuration » (parameterless), ce qui avait été un problème fréquent de solutions comme RED. (Mon opinion personnelle est que CoDel n'est pas réellement sans configuration, comme on le voit plus loin dans le RFC, mais il est certainement « configuration minimale ».) CoDel s'ajuste tout seul, une fois défini l'ordre de grandeur du RTT des paquets qui passeront par le routeur.
  • Capable de différencier le mauvais chasseur du bon chasseur, euh, pardon, la mauvaise file d'attente de la bonne.
  • Être simple à programmer, pour fonctionner aussi bien dans les processeurs limités des routeurs low cost que dans les ASIC (rapides, mais pas très souples) des routeurs haut de gamme.

Lorsque CoDel estime nécessaire de prendre des mesures (le trafic entre trop vite), il peut jeter les paquets, ou les marquer avec ECN (RFC 3168).

La section 2 de notre RFC définit la terminologie de CoDel. Parmi les termes importants :

  • Temps de passage (sojourn time) : le temps passé par le paquet dans la file d'attente. C'est la donnée de base de CoDel, qui va essayer de minimiser ce temps de passage.
  • File persistante (standing queue) : une file d'attente qui reste pleine trop longtemps, « trop » étant de l'ordre du RTT le plus élevé parmi les flots des paquets qui attendent dans la file.

Passons maintenant à une description de haut niveau de CoDel. Son but est de différencier la mauvaise file (qui ne fait qu'ajouter du retard d'acheminement des paquets) de la bonne. Une file d'attente se forme lorsqu'il y a un goulet d'étranglement, parce qu'un lien à forte capacité se déverse dans un lien à faible capacité, ou bien parce que plusieurs liens convergent vers un lien ayant la capacité de seulement l'un d'eux. Une notion importante à ce sujet est celle de BDP, en gros le nombre d'octets en transit pour une connexion donnée, lorsque le débit atteint 100 % de la capacité. Imaginons une connexion TCP dont la fenêtre d'envoi est de 25 paquets (je sais bien que les fenêtres TCP se calculent en octets, pas en paquets, mais c'est le RFC qui fait cet abus de langage, pas moi) et où le BDP est de 20 paquets. En régime permanent, il y aura 5 paquets dans la file d'attente. Si la fenêtre est de 30 paquets, ce seront 10 paquets qui occuperont en permanence la file d'attente, augmentant encore le délai, alors que le débit ne changera pas (20 paquets arriveront par RTT). Au contraire, si on réduit la fenêtre à 20 paquets, la file d'attente sera vide, le délai sera réduit au minimum, alors que le débit n'aura pas changé. Ce résultat contre-intuitif montre que bourrer la connexion de paquets n'est pas forcément le meilleur moyen d'aller « vite ». Et que la taille de la file ne renseigne pas sur le rythme d'envoi des données. Et enfin que les files pleines en permanence n'apportent que du délai. Dans le premier exemple, la file contenant cinq paquets tout le temps était clairement une mauvaise file. Un tampon d'entrée/sortie de 20 paquets est pourtant une bonne chose (pour absorber les variations brutales) mais, s'il ne se vide pas complètement ou presque en un RTT, c'est qu'il est mal utilisé. Répétons : Les bonnes files se vident vite.

CoDel comporte trois composants : un estimateur, un objectif et une boucle de rétroaction. La section 3 de notre RFC va les présenter successivement. Pour citer l'exposé de Van Jacobson à une réunion IETF, ces trois composants sont :

  • a) Measure what you’ve got
  • b) Decide what you want
  • c) If (a) isn’t (b), move it toward (b)

D'abord, l'estimateur. C'est la partie de CoDel qui observe la file d'attente et en déduit si elle est bonne ou mauvaise. Autrefois, la principale métrique était la taille de la file d'attente. Mais celle-ci peut varier très vite, le trafic Internet étant très irrégulier. CoDel préfère donc observer le temps de séjour dans la file d'attente. C'est une information d'autant plus essentielle qu'elle a un impact direct sur le vécu de l'utilisateur, via l'augmentation de la latence.

Bon, et une fois qu'on observe cette durée de séjour, comment en déduit-on que la file est bonne ou mauvaise ? Notre RFC recommande de considérer la durée de séjour minimale. Si elle est nulle (c'est-à-dire si au moins un paquet n'a pas attendu du tout dans la file, pendant la dernière période d'observation), alors il n'y a pas de file d'attente permanente, et la file est bonne.

Le RFC parle de « période d'observation ». Quelle doit être la longueur de cette période ? Au moins un RTT pour être sûr d'écluser les pics de trafic, mais pas moins pour être sûr de détecter rapidement les mauvaises files. Le RFC recommande donc de prendre comme longueur le RTT maximal de toutes les connexions qui empruntent ce tampon d'entrée/sortie.

Et, petite astuce d'implémentation (un routeur doit aller vite, et utilise souvent des circuits de calcul plus simples qu'un processeur généraliste), on peut calculer la durée de séjour minimale avec une seule variable : le temps écoulé depuis lequel la durée de séjour est inférieure ou supérieure au seuil choisi. (Dans le pseudo-code de la section 5, et dans le noyau Linux, c'est à peu près le rôle de first_above_time.)

Si vous aimez les explications avec images, il y en a plein dans cet excellent exposé à l'IETF.

Ensuite, l'objectif (appelé également référence) : il s'agit de fixer un objectif de durée de séjour dans la file. Apparemment, zéro serait l'idéal. Mais cela entrainerait des « sur-réactions », où on jetterait des paquets (et ralentirait TCP) dès qu'une file d'attente se forme. On va plutôt utiliser un concept dû à l'inventeur du datagramme, Leonard Kleinrock, dans « An Invariant Property of Computer Network Power », la « puissance » (power). En gros, c'est le débit divisé par le délai et l'endroit idéal, sur la courbe de puissance, est en haut (le maximum de débit, pour le minimum de délai). Après une analyse que je vous épargne, le RFC recommande de se fixer comme objectif entre 5 et 10 % du RTT.

Enfin, la boucle de rétroaction. Ce n'est pas tout d'observer, il faut agir. CoDel s'appuie sur la théorie du contrôle, pour un système MIMO (Multi-Input Multi-Output). Placé à la fin de la file d'attente, au moment où on décide quoi faire des paquets, ce contrôleur va les jeter (ou les marquer avec ECN) si l'objectif de durée de séjour est dépassé.

Passons maintenant à la section 4 du RFC, la plus concrète, puisqu'elle décrit précisement CoDel. L'algorithme a deux variables, TARGET et INTERVAL (ces noms sont utilisés tels quels dans le pseudo-code en section 5, et dans l'implémentation dans le noyau Linux). TARGET est l'objectif (le temps de séjour dans la file d'attente qu'on ne souhaite pas dépasser) et INTERVAL est la période d'observation. Ce dernier est également le seul paramètre de CoDel qu'il faut définir explicitement. Le RFC recommande 100 ms par défaut, ce qui couvre la plupart des RTT qu'on rencontre dans l'Internet, sauf si on parle à des gens très lointains ou si on passe par des satellites (cf. M. Dischinger, « Characterizing Residential Broadband Networks », dans les Proceedings of the Internet Measurement Conference, San Diego, en 2007, si vous voulez des mesures). Cela permet, par exemple, de vendre des routeurs bas de gamme, genre point d'accès Wifi sans imposer aux acheteurs de configurer CoDel.

Si on est dans un environnement très différent de celui d'un accès Internet « normal », il peut être nécessaire d'ajuster les valeurs (CoDel n'est donc pas réellement « parameterless »). L'annexe A du RFC en donne un exemple, pour le cas du centre de données, où les latences sont bien plus faibles (et les capacités plus grandes) ; INTERVAL peut alors être défini en microsecondes plutôt qu'en millisecondes.

TARGET, lui, va être déterminé dynamiquement par CoDel. Une valeur typique sera aux alentours de 5 ms (elle dépend évidemment de INTERVAL) : si, pendant une durée égale à INTERVAL, les paquets restent plus longtemps que cela dans la file d'attente, c'est que c'est une mauvaise file, on jette des paquets. Au passage, dans le noyau Linux, c'est dans codel_params_init que ces valeurs sont fixées :

 	params->interval = MS2TIME(100);
	params->target = MS2TIME(5);
   

Les programmeurs apprécieront la section 5, qui contient du pseudo-code, style C++, mettant en œuvre CoDel. Le choix de C++ est pour profiter de l'héritage, car CoDel est juste une dérivation d'un classique algorithme tail-drop. On peut donc le programmer sous forme d'une classe qui hérite d'une classe queue_t, plus générale.

De toute façon, si vous n'aimez pas C++, vous pouvez lire le code source du noyau Linux, qui met en œuvre CoDel depuis longtemps (fichier net/sched/sch_codel.c, également accessible via le Web.

(Pour comprendre les exemples de code, notez que packet_t* pointe vers un descripteur de paquet, incluant entre autres un champ, tstamp, qui stocke un temps, et que le type time_t est exprimé en unités qui dépendent de la résolution du système, sachant que la milliseconde est suffisante, pour du trafic Internet « habituel ».) Presque tout le travail de CoDel est fait au moment où le paquet sort de la file d'attente. À l'entrée, on se contente d'ajouter l'heure d'arrivée, puis on appelle le traitement habituel des files d'attente :

void codel_queue_t::enqueue(packet_t* pkt)
   {
       pkt->tstamp = clock();
       queue_t::enqueue(pkt);
   }       
     

Le gros du code est dans le sous-programme dodequeue qui va déterminer si le paquet individuel vient de nous faire changer d'état :


dodequeue_result codel_queue_t::dodequeue(time_t now)
   {
   ...
       // Calcul de *la* variable importante, le temps passé dans la
       // file d'attente
       time_t sojourn_time = now - r.p->tstamp;
       // S'il est inférieur à l'objectif, c'est bon
       if (sojourn_time_ < TARGET || bytes() <= maxpacket_) {
           first_above_time_ = 0;
       } else {
           // Aïe, le paquet est resté trop longtemps (par rapport à TARGET)
           if (first_above_time_ == 0) {
	       // Pas de panique, c'est peut-être récent, attendons
	       // INTERVAL avant de décider
               first_above_time_ = now + INTERVAL;
           } else if (now >= first_above_time_) {
               // La file ne se vide pas : jetons le paquet
               r.ok_to_drop = true;
           }
       }
       return r;
   }

     

Le résultat de dodequeue est ensuite utilisé par dequeue qui fait se fait quelques réflexions supplémentaires avant de jeter réellement le paquet.

Ce code est suffisamment simple pour pouvoir être mis en œuvre dans le matériel, par exemple ASIC ou NPU.

L'annexe A du RFC donne un exemple de déclinaison de CoDel pour le cas d'un centre de données, où les RTT typiques sont bien plus bas que sur l'Internet public. Cette annexe étudie également le cas où on applique CoDel aux files d'attente des serveurs, pas uniquement des routeurs, avec des résultats spectaculaires :

  • Dans le serveur, au lieu de jeter le paquet bêtement, on prévient directement TCP qu'il doit diminuer la fenêtre,
  • Le RTT est connu du serveur, et peut donc être mesuré au lieu d'être estimé (on en a besoin pour calculer INTERVAL),
  • Les paquets de pur contrôle (ACK, sans données) ne sont jamais jetés, car ils sont importants (et de petite taille, de toute façon).

Sur l'Internet public, il serait dangereux de ne jamais jeter les paquets de pur contrôle, ils pourraient être envoyés par un attaquant. Mais, dans le serveur, aucun risque.


Téléchargez le RFC 8289


L'article seul

RFC 8307: Well-Known URIs for the WebSocket Protocol

Date de publication du RFC : Janvier 2018
Auteur(s) du RFC : C. Bormann (Universitaet Bremen TZI)
Chemin des normes
Première rédaction de cet article le 3 janvier 2018


Il existe une norme pour un préfixe de chemin dans un URI, préfixe nommée .well-known, et après lequel plusieurs noms sont normalisés, pour des ressources « bien connues », c'est-à-dire auxquelles on peut accéder sans lien qui y mène. Le RFC 5785 normalise ce .well-known. Il n'était prévu à l'origine que pour les plans http: et https:. Ce très court RFC l'étend aux plans ws: et wss:, ceux des Web sockets du RFC 6455.

Les gens de CoAP avaient déjà étendu l'usage de .well-known en permettant (RFC 7252) qu'il soit utilisé pour les plans coap: et coaps:.

Il existe un registre IANA des suffixes (les termes après .well-known). Ce registre est le même quel que soit le plan d'URI utilisé. Il ne change donc pas suite à la publication de ce RFC.


Téléchargez le RFC 8307


L'article seul

Fiche de lecture : Le médecin qui voulut être roi

Auteur(s) du livre : Guillaume Lachenal
Éditeur : Seuil
9-782021-142563
Publié en 2017
Première rédaction de cet article le 3 janvier 2018


Voici un excellent livre sur le colonialisme, à travers le cas d'un médecin français, Jean-Joseph David, qui, en poste à Wallis, puis au Cameroun, a obtenu les pleins pouvoirs, et s'est lancé dans une utopie, créer une société parfaite.

C'est une banalité de parler de pouvoir médical et de dire que le médecin se sent souvent placé au dessus des autres humains. Mais ici, dans les deux pays, David a en effet réussi à avoir la totalité des pouvoirs, médical, mais aussi économique, administratif, policier et judiciaire (il pouvait punir, et il ne s'en privait pas). Tout partait pourtant d'une idée sympathique, celle de la « médecine sociale », l'idée, en rupture avec la médecine pasteurienne, qu'on ne pouvait pas considérer la maladie comme le résultat de la seule action des microbes, mais qu'il fallait prendre en compte tout l'environnement. Mais cette idée a vite mené à un dérapage qu'on doit pouvoir qualifier de totalitaire. Le médecin s'est en effet senti en droit de réorganiser toute la société selon ses vues, évidemment sans jamais demander leur avis aux habitants.

Jean-Joseph David était-il gentil ou méchant ? Il traitait bien les indigènes, mais comme un éleveur traite bien son troupeau. Il ne les considérait pas comme des citoyens, même pas complètement comme des humains, quand il parlait d'« améliorer la race » ou bien d'amener beaucoup d'enfants à l'âge adulte pour que la France ait beaucoup de tirailleurs. Et ses utopies ont parfois eu des conséquences graves. Il était clairement mégalomane (« la mégalomanie est une maladie tropicale », note un ancien des interventions en Afrique à l'auteur) mais était aussi le produit de son époque (la culture du Pharo, où tous ces médecins coloniaux étaient formés par d'anciens coloniaux) et de l'éloignement (depuis Yaoundé, le gouverneur français le laissait faire ce qu'il voulait).

Le titre du livre fait évidemment référence à Kipling mais, ici, le héros ne cherche pas juste à être roi et à en avoir les avantages, il veut vraiment transformer la société.

Le livre est un peu décousu, d'abord parce qu'on a peu d'archives sur David (une grande partie a disparu dans les troubles qu'a connu le Cameroun par la suite), ensuite parce que ce n'est pas un roman, mais un travail plutôt universitaire, avec beaucoup de notions de sociologie et d'anthropologie qui nécessiteront que l·e·a lect·eur·rice s'accroche. Mais cela vaut la peine, il y a eu peu de descriptions de ce genre d'« utopies coloniales » où un mégalomane, profitant du pouvoir que lui donnait la force militaire de la France, se disait « je vais enfin pouvoir remodeler la société ». On est choqués de sa dictature et en même temps, c'est fascinant de se demander « et moi, si j'avais un tel pouvoir, j'en ferais quoi ? »


L'article seul

Fiche de lecture : La mer des Cosmonautes

Auteur(s) du livre : Cédric Gras
Éditeur : Paulsen
Publié en 2017
Première rédaction de cet article le 1 janvier 2018


Il fait froid dehors, en ce début 2018 ? Voici un livre pour se réchauffer : partez en Antarctique avec les polyarniks russes.

Ce n'est pas un roman, mais un témoignage de l'auteur, qui est allé en Antarctique (en été, quand même…) à bord du Akademik Fedorov et en a rapporté plein d'histoires de travailleurs polaires. Et, oui, la mer des Cosmonautes du titre existe vraiment.

Le temps des explorateurs polaires avec trois types et quinze chiens qui se lançaient à l'assaut du continent blanc est passé : les bases sont maintenant bien plus grandes et peuplées, avec une débauche de matériel. Dans les bases russes, on trouve tous les fleurons de l'ex-industrie soviétique, devenues souvent épaves, de l'Iliouchine 14 au GAZ-69. On est maintenant dans l'exploitation plus que l'exploration. Mais les conditions de vie restent difficiles : le matériel casse, les humains s'épuisent, l'accident guette dès qu'on sort, il faut parfois allumer un feu sous le bulldozer pour qu'il démarre. Et les Russes prennent apparemment moins de précautions avec le matériel humain, alimentant le cimetière qui jouxte une des bases.

Mais pourquoi trouve-t-on des volontaires pour l'Antarctique ? À l'époque soviétique, une des raisons était le prestige attaché au métier de polyarnik. Leur prestige ne le cédait qu'aux cosmonautes, héros souvent cités par les polyarniks nostalgiques « à cette époque, on était quasiment aussi adulés que les cosmonautes », et les gamins dans les cours d'école jouaient en alternance au cosmonaute et au travailleur polaire. Tout ceci s'est effondré avec l'Union Soviétique, au point que, dans certaines bases, il a fallu vendre les breloques à étoile rouge aux touristes états-uniens de la base voisine, pour pouvoir se financer. Il n'y a plus qu'en Biélorussie, gelée dans les années Brejnev, que les polyarniks sont encore accueillis avec médailles et discours, à leur retour.

Aujourd'hui, la Russie continue l'exploitation antarctique, mais avec un nombre de bases réduit. Des nouveaux pays sont venus, et on s'échange les visites de courtoisie, dans ce territoire démilitarisé. Le travailleur polaire continue de bénéficier d'avantages matériels conséquents, et garde un peu de prestige, pas à cause de l'Antarctique, mais à cause des escales exotiques que fait le navire avant d'atteindre le continent blanc. Et puis, certains travailleurs le confient à l'auteur : après plusieurs rotations en Antarctique, ils ne se voient pas vraiment reprendre une vie « normale ».

En ligne, les photos de l'auteur.


L'article seul

Géolocation d'une adresse IP via le DNS

Première rédaction de cet article le 24 décembre 2017


Pour Noël, un truc complètement inutile mais amusant : trouver la longitude et la latitude d'une adresse IP via le DNS.

Ce service est fourni par Bert Hubert, l'auteur de PowerDNS, via le domaine geo.lua.powerdns.org. (Il ne marche apparemment que pour IPv4.) On inverse l'adresse IP (comme pour in-addr.arpa) et on fait une requête pour le type TXT. Exemple avec l'adresse du serveur Web de l'AFNIC, 192.134.5.24 :

      
% dig TXT 24.5.134.192.geo.lua.powerdns.org
...;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40951
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 1
...
;; ANSWER SECTION:
24.5.134.192.geo.lua.powerdns.org. 3600	IN TXT "48.858200 2.338700"

    

On peut automatiser l'opération avec awk (merci à Oli Schacher‏) :

% reverse() { echo $1  | awk -F. '{print $4"."$3"." $2"."$1}' }

% reverse 192.134.5.24
24.5.134.192
    

Une fois que c'est fait, on peut lancer un navigateur Web directement vers la carte, ici avec OpenStreetMap :


% show-ip() { x-www-browser https://www.openstreetmap.org/\?$(dig  $(echo $1 | awk -F. '{print $4"."$3"." $2"."$1}').geo.lua.powerdns.org TXT +short | head -1 | awk '{gsub(/"/, ""); print "mlat="$1"&mlon="$2"&zoom=12"}')}         

% show-ip 192.134.5.24

    

Et si vous préférez Evil Corp. :


% show-ip() { x-www-browser https://maps.google.com/maps\?q=$(dig  $(echo $1 | awk -F. '{print $4"."$3"." $2"."$1}').geo.lua.powerdns.org TXT +short | awk '{gsub(/"/, ""); print $1","$2}')}

    

La base de géolocalisation utilisée est celle de MaxMind qui, comme toutes les bases de géolocalisation vaut ce qu'elle vaut (le serveur Web de l'AFNIC n'est pas au centre de Paris…)


L'article seul

RFC 8305: Happy Eyeballs Version 2: Better Connectivity Using Concurrency

Date de publication du RFC : Décembre 2017
Auteur(s) du RFC : D. Schinazi, T. Pauly (Apple)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 21 décembre 2017


Une machine connectée à l'Internet et répondant aux requêtes venues du réseau a souvent plusieurs adresses IP pour son nom. C'est parfois une adresse IPv4 et une IPv6 mais cela peut aussi être plusieurs adresses IPv6, ou bien un mélange en proportions quelconques. Les développeurs d'application et les administrateurs système qui déploieront ces applications ensuite, ont un choix difficile si certaines de ces adresses marchent et d'autres pas (ou mal). Si les différentes adresses IP de cette machine passent par des chemins différents, certains marchant et d'autres pas, l'application arrivera-t-elle à se rabattre sur une autre adresse très vite ou bien imposera-t-elle à l'utilisateur un long délai avant de détecter enfin le problème ? Cette question est connue comme « le bonheur des globes oculaires » (les dits globes étant les yeux de l'utilisateur qui attend avec impatience la page d'accueil de PornHub) et ce RFC spécifie les exigences pour l'algorithme de connexion du client. En les suivant, les globes oculaires seront heureux. Il s'agit de la version 2 de l'algorithme, bien plus élaborée que la version 1 qui figurait dans le RFC 6555.

La section 1 rappelle les données du problème : on veut évidemment que cela marche aussi bien en IPv6 (RFC 8200) qu'en IPv4 (pas question d'accepter des performances inférieures) or, dans l'état actuel du déploiement d'IPv6, bien des sites ont une connexion IPv6 totalement ou partiellement cassée. Si un serveur a IPv4 et IPv6 et que son client n'a qu'IPv4, pas de problème. Mais si le client a IPv6, tente de l'utiliser, mais que sa connexion est plus ou moins en panne, ou simplement sous-optimale, ses globes oculaires vont souffrir d'impatience. On peut aussi noter que le problème n'est pas spécifique à IPv6 : du moment que la machine visée a plusieurs adresses, qu'elles soient IPv4 ou IPv6, le risque que certaines des adresses ne marchent pas (ou moins bien) existe, et l'algorithme des globes oculaires heureux doit être utilisé. (C'est un des gros changements avec le précédent RFC, le RFC 6555, qui n'envisageait que le cas IPv6.)

La bonne solution est donc que l'application elle-même gère le problème (ou, sinon l'application elle-même, la bibliothèque logicielle qu'elle utilise et où se trouve la fonction de connexion). Il existe plusieurs algorithmes pour cela, déjà largement déployés depuis des années. On peut donc se baser sur l'expérience pour spécifier ces algorithmes. Ce RFC normalise les caractéristiques que doivent avoir ces algorithmes. Si on suit ce RFC, le trafic (IP et DNS) va légèrement augmenter (surtout si la connectivité IPv6 marche mal ou pas du tout) mais la qualité du vécu de l'utilisateur va être maintenue, même en présence de problèmes, ce qui compense largement. Autrement, il existerait un risque élevé que certains utilisateurs coupent complètement IPv6, plutôt que de supporter ces problèmes de délai de connexion.

La cible principale de notre RFC est composée des protocoles de transport avec connexion (TCP, SCTP), les protocoles sans connexion comme UDP soulevant d'autres questions (s'ils ont une sémantique requête/réponse, comme dans ICE, les algorithmes de ce RFC peuvent être utilisés).

Donc, on a un nom de machine qu'on veut contacter, mettons www.example.com, avec plusieurs adresses associées, peut-être de familles (v4 et v6) différentes. Prenons une machine ayant une seule adresse IPv4 et une seule adresse IPv6, avec une connexion IPv6 qui marche mal. Avec l'algorithme naïf qu'utilisent encore certains logiciels voici la séquence d'évenements traditionnelle :

  • L'initiateur de la connexion utilise le DNS pour demander les enregistrements A (adresse IPv4) et AAAA (IPv6).
  • Il récupère 192.0.2.1 et 2001:db8::1.
  • Il tente IPv6 (sur Linux, l'ordre des essais est réglable dans /etc/gai.conf). L'initiateur envoie un paquet TCP SYN à 2001:db8::1.
  • Pas de réponse (connexion IPv6 incorrecte). L'initiateur réessaie, deux fois, trois fois, faisant ainsi perdre de nombreuses secondes.
  • L'initiateur renonce, il passe à IPv4 et envoie un paquet TCP SYN à 192.0.2.1.
  • Le répondeur envoie un SYN+ACK en échange, l'initiateur réplique par un ACK et la connexion TCP est établie.

Le problème de cet algorithme naïf est donc la longue attente lors des essais IPv6. On veut au contraire un algorithme qui bascule rapidement en IPv4 lorsqu'IPv6 ne marche pas, sans pour autant gaspiller les ressources réseau en essayant par exemple toutes les adresses en même temps.

L'algorithme recommandé (sections 3 à 5, cœur de ce RFC) aura donc l'allure suivante :

  • L'initiateur de la connexion utilise le DNS pour demander les enregistrements A (adresse IPv4) et AAAA (IPv6).
  • Il récupère 192.0.2.1 et 2001:db8::1. Il sait donc qu'il a plusieurs adresses, de famille différente.
  • Il tente IPv6 (l'algorithme du RFC est de toute façon facilement adaptable à des cas où IPv4 est prioritaire). L'initiateur envoie un paquet TCP SYN à 2001:db8::1, avec un très court délai de garde.
  • Pas de réponse quasi-immédiate ? L'initiateur passe à IPv4 rapidement. Il envoie un paquet TCP SYN à 192.0.2.1.
  • Le répondeur envoie un SYN+ACK en échange, l'initiateur réplique par un ACK et la connexion TCP est établie.

Si le répondeur réagit à une vitesse normale en IPv6, la connexion sera établie en IPv6. Sinon, on passera vite en IPv4, et l'utilisateur humain ne s'apercevra de rien. Naturellement, si le DNS n'avait rapporté qu'une seule adresse (v4 ou v6), on reste à l'algorithme traditionnel (« essayer, patienter, ré-essayer »).

Maintenant, les détails. D'abord, le DNS (section 3 de notre RFC). Pour récupérer les adresses appartenant aux deux familles (IPv4 et IPv6), il faut envoyer deux requêtes, de type A et AAAA. Pas de délai entre les deux, et le AAAA en premier, recommande le RFC. Notez qu'il n'existe pas de type de requête DNS pour avoir les deux enregistrements d'un coup, il faut donc deux requêtes.

Il ne faut pas attendre d'avoir la réponse aux deux avant de commencer à tenter d'établir une connexion. En effet, certains pare-feux configurés avec les pieds bloquent les requêtes AAAA, qui vont finir par timeouter. Du point de vue du programmeur, cela signifie qu'il faut faire les deux requêtes DNS dans des fils différents (ou des goroutines différentes en Go), ou bien, utiliser une API asynchrone, comme getdns. Ensuite, si on reçoit la réponse AAAA mais pas encore de A, on essaye tout de suite de se connecter, si on a la réponse A, on attend quelques millisecondes la réponse AAAA puis, si elle ne vient pas, tant pis, on essaie en IPv4. (La durée exacte de cette attente est un des paramètres réglables de l'algorithme. Il se nomme Resolution Delay et sa valeur par défaut recommandée est de 50 ms.)

À propos de DNS, notez que le RFC recommande également de privilégier IPv6 pour le transport des requêtes DNS vers les résolveurs (on parle bien du transport des paquets DNS, pas du type des données demandées). Ceci dit, ce n'est pas forcément sous le contrôle de l'application.

Une fois récupérées les adresses, on va devoir les trier selon l'ordre de préférence. La section 4 décrit comment cela se passe. Rappelons qu'il peut y avoir plusieurs adresses de chaque famille, pas uniquement une v4 et une v6, et qu'il est donc important de gérer une liste de toutes les adresses reçues (imaginons qu'on ne récupère que deux adresses v4 et aucune v6 : l'algorithme des globes oculaires heureux est quand même crucial car il est parfaitement possible qu'une des adresses v4 ne marche pas).

Pour trier, le RFC recommande de suivre les règles du RFC 6724, section 6. Si le client a un état (une mémoire des connexions précédentes, ce qui est souvent le cas chez les clients qui restent longtemps à tourner, un navigateur Web, par exemple), il peut ajouter dans les critères de tri le souvenir des succès (ou échecs) précédents, ainsi que celui des RTT passés. Bien sûr, un changement de connectivité (détecté par le DNA des RFC 4436 ou RFC 6059) doit entraîner un vidage complet de l'état (on doit oublier ce qu'on a appris, qui n'est plus pertinent).

Dernier détail sur le tri : il faut mêler les adresses des deux familles. Imaginons un client qui récupère trois adresses v6 et trois v4, client qui donne la priorité à IPv4, mais dont la connexion IPv4 est défaillante. Si sa liste d'adresses à tester comprend les trois adresses v4 en premier, il devra attendre trois essais avant que cela ne marche. Il faut donc plutôt créer une liste {une adressse v4, une adresse v6, une adresse v4…}. Le nombre d'adresses d'une famille à inclure avant de commencer l'autre famille est le paramètre First Address Family Count, et il vaut un par défaut.

Enfin, on essaie de se connecter en envoyant des paquets TCP SYN (section 5). Il est important de ne pas tester IPv4 tout de suite. Les premiers algorithmes « bonheur des globes oculaires » envoyaient les deux paquets SYN en même temps, gaspillant des ressources réseau et serveur. Ce double essai faisait que les équipements IPv4 du réseau avaient autant de travail qu'avant, alors qu'on aurait souhaité les retirer du service petit à petit. En outre, ce test simultané fait que, dans la moitié des cas, la connexion sera établie en IPv4, empêchant de tirer profit des avantages d'IPv6 (cf. RFC 6269). Donc, on doit tester en IPv6 d'abord, sauf si on se souvient des tentatives précédentes (voir plus loin la variante « avec état ») ou bien si l'administrateur système a délibérement configuré la machine pour préférer IPv4.

Après chaque essai, on attend pendant une durée paramétrable, Connection Attempt Delay, 250 ms par défaut (bornée par les paramètres Minimum Connection Attempt Delay, 100 ms par défaut, qu'on ne devrait jamais descendre en dessous de 10 ms, et Maximum Connection Attempt Delay, 2 s par défaut).

L'avantage de cet algorithme « IPv6 d'abord puis rapidement basculer en IPv4 » est qu'il est sans état : l'initiateur n'a pas à garder en mémoire les caractéristiques de tous ses correspondants. Mais son inconvénient est qu'on recommence le test à chaque connexion. Il existe donc un algorithme avec état (cf. plus haut), où l'initiateur peut garder en mémoire le fait qu'une machine (ou bien un préfixe entier) a une adresse IPv6 mais ne répond pas aux demandes de connexion de cette famille. Le RFC recommande toutefois de re-essayer IPv6 au moins toutes les dix minutes, pour voir si la situation a changé.

Une conséquence de l'algorithme recommandé est que, dans certains cas, les deux connexions TCP (v4 et v6) seront établies (si le SYN IPv6 voyage lentement et que la réponse arrive après que l'initiateur de la connexion se soit impatienté et soit passé à IPv4). Cela peut être intéressant dans certains cas rares, mais le RFC recommande plutôt d'abandonner la connexion perdante (la deuxième). Autrement, cela pourrait entraîner des problèmes avec, par exemple, les sites Web qui lient un cookie à l'adresse IP du client, et seraient surpris de voir deux connexions avec des adresses différentes.

La section 9 du RFC rassemble quelques derniers problèmes pratiques. Par exemple, notre algorithme des globes oculaires heureux ne prend en compte que l'établissement de la connexion. Si une adresse ne marche pas du tout, il choisira rapidement la bonne. Mais si une adresse a des problèmes de MTU et pas l'autre, l'établissement de la connexion, qui ne fait appel qu'aux petits paquets TCP SYN, se passera bien alors que le reste de l'échange sera bloqué. Une solution possible est d'utiliser l'algorithme du RFC 4821.

D'autre part, l'algorithme ne tient compte que de la possibilité d'établir une connexion TCP, ce qui se fait typiquement uniquement dans le noyau du système d'exploitation du serveur. L'algorithme ne garantit pas qu'une application écoute, et fonctionne.

Parmi les problèmes résiduels, notez que l'algorithme des globes oculaires heureux est astucieux, mais tend à masquer les problèmes (section 9.3). Si un site Web publie les deux adresses mais que sa connectivité IPv6 est défaillante, aucun utilisateur ne lui signalera puisque, pour eux, tout va bien. Il est donc recommandé que l'opérateur fasse des tests de son côté pour repérer les problèmes (le RFC 6555 recommandait que le logiciel permette de débrayer cet algorithme, afin de tester la connectivité avec seulement v4 ou seulement v6, ou bien que le logiciel indique quelque part ce qu'il a choisi, pour mieux identifier d'éventuels problèmes v6.)

Pour le délai entre le premier SYN IPv6 et le premier SYN IPv4, la section 5 donne des idées quantitatives en suggérant 250 ms entre deux essais. C'est conçu pour être quasiment imperceptible à un utilisateur humain devant son navigateur Web, tout en évitant de surcharger le réseau inutilement. Les algorithmes avec état ont le droit d'être plus impatients, puisqu'ils peuvent se souvenir des durées d'établissement de connexion précédents.

Notez que les différents paramètres réglables indiqués ont des valeurs par défaut, décrites en section 8, et qui ont été déterminées empiriquement.

Si vous voulez une meilleure explication de la version 2 des globes oculaires heureux, il y a cet exposé au RIPE.

Enfin, les implémentations. Notez que les vieilles mises en œuvre du RFC 6555 (et présentées à la fin de mon précédent article) sont toujours conformes à ce nouvel algorithme, elles n'en utilisent simplement pas les raffinements. Les versions récentes de macOS (Sierra) et iOS (10) mettent en œuvre notre RFC, ce qui est logique, puisqu'il a été écrit par des gens d'Apple (l'annonce est ici, portant même sur des versions antérieures). Apple en a d'ailleurs profité pour breveter cette technologie. À l'inverse, un exemple récent de logiciel incapable de gérer proprement le cas d'un pair ayant plusieurs adresses IP est Mastodon (cf. bogue #3762.)

Dans l'annexe A, vous trouverez la liste complète des importants changements depuis le RFC 6555. Le précédent RFC n'envisageait qu'un seul cas, deux adresses IP, une en v4, l'autre en v6. Notre nouveau RFC 8305 est plus riche, augmente le parallélisme, et ajoute :

  • La façon de faire les requêtes DNS (pour tenir compte des serveurs bogués qui ne répondent pas aux requêtes AAAA, cf. RFC 4074),
  • La gestion du cas où il y a plusieurs adresses IP de la même famille (v4 ou v6),
  • La bonne façon d'utiliser les souvenirs des connexions précédentes,
  • Et la méthode (dont je n'ai pas parlé ici) pour le cas des réseaux purement IPv6, mais utilisant le NAT64 du RFC 8305.

Téléchargez le RFC 8305


L'article seul

RFC 8297: An HTTP Status Code for Indicating Hints

Date de publication du RFC : Décembre 2017
Auteur(s) du RFC : K. Oku (Fastly)
Expérimental
Réalisé dans le cadre du groupe de travail IETF httpbis
Première rédaction de cet article le 20 décembre 2017


Lorsqu'un serveur HTTP répond, la réponse contient souvent des liens vers d'autres ressources. Un exemple typique est celui de la page Web dont le chargement va déclencher le chargement de feuilles de style, de JavaScript, etc. Pour minimiser la latence, il serait intéressant de prévenir le client le plus tôt possible. C'est le but de ce RFC, qui décrit le code de retour intérimaire 103, qui prévient le client qu'il peut tout de suite commencer à charger ces ressources supplémentaires.

Il existe un type de lien pour cela, preload, décrit par ses auteurs et enregistré dans le registre des types de liens (cf. RFC 8288). Il peut être utilisé dans la réponse « normale » :


HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </main.css>; rel="preload"; as="style"
Link: </script.js>; rel="preload"; as="script"    

    

Mais cela ne fait pas gagner grand'chose : une toute petite fraction de seconde après, le client HTTP verra arriver le source HTML et pourra y découvrir les liens. On voudrait renvoyer tout de suite la liste des ressources à charger, sans attendre que le serveur ait fini de calculer la réponse (ce qui peut prendre du temps, s'il faut dérouler mille lignes de Java et plein de requêtes SQL…)

Le nouveau code de retour, 103, lui, peut être envoyé immédiatement, avec la liste des ressources. Le client peut alors les charger, tout en attendant le code de retour 200 qui indiquera que la ressource principale est prête. (Les codes de retour commençant par 1, comme 103, sont des réponses temporaires, « pour information », en attendant le succès, annoncé par un code commençant par 2. Cf. RFC 7231, sections 6.2 et 6.3.) La réponse HTTP utilisant le nouveau code ressemblera à :


HTTP/1.1 103 Early Hints
Link: </main.css>; rel="preload"; as="style"
Link: </script.js>; rel="preload"; as="script"

HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </main.css>; rel="preload"; as="style"
Link: </script.js>; rel="preload"; as="script"    

Les détails, maintenant (section 2 du RFC). 103 indique au client qu'il y aura une série de liens vers des ressources supplémentaires qu'il peut être intéressant, par exemple, de charger tout de suite. Les liens finaux seront peut-être différents (dans l'exemple ci-dessus, ils sont identiques). 103 est juste une optimisation, pas une obligation. (Hint = suggestion.) Les liens qu'il indique ne font pas autorité. Le serveur peut indiquer des liens supplémentaires, ne pas indiquer des liens qui étaient dans la réponse 103, indiquer des liens différents, etc.

Il peut même y avoir plusieurs 103 à la suite, notamment si un relais sur le trajet ajoute le sien, par exemple en se basant sur une réponse qu'il avait gardée en mémoire. 103 n'est en effet pas toujours envoyé par le serveur d'origine de la ressource, il peut l'être par un intermédiaire. Voici un exemple qui donne une idée des variantes possibles :


HTTP/1.1 103 Early Hints
Link: </main.css>; rel="preload"; as="style"

HTTP/1.1 103 Early Hints
Link: </style.css>; rel="preload"; as="style"
Link: </script.js>; rel="preload"; as="script"

HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </main.css>; rel="preload"; as="style"
Link: </newstyle.css>; rel="preload"; as="style"
Link: </script.js>; rel="preload"; as="script"

On voit que la réponse finale n'est ni la première suggestion, ni la deuxième (ni une union des deux).

Note pour les programmeurs Python/WSGI. Je ne suis pas arrivé à utiliser ce code « intérimaire » avec WSGI, cela ne semble pas possible en WSGI. Mais on trouvera sans doute d'autres implémentations…

Le code 103 est désormais dans le registre IANA des codes de retour.


Téléchargez le RFC 8297


L'article seul

RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format

Date de publication du RFC : Décembre 2017
Auteur(s) du RFC : T. Bray (Textuality)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF jsonbis
Première rédaction de cet article le 14 décembre 2017


Il existe une pléthore de langages pour décrire des données structurées. JSON, normalisé dans ce RFC (qui succède au RFC 7159, avec peu de changements), est actuellement le plus à la mode. Comme son concurrent XML, c'est un format textuel, et il permet de représenter des structures de données hiérarchiques.

À noter que JSON doit son origine, et son nom complet (JavaScript Object Notation) au langage de programmation JavaScript, dont il est un sous-ensemble (enfin, approximativement). La norme officielle de JavaScript est à l'ECMA, dans ECMA-262. JSON est dans la section 24.5 de ce document mais est aussi dans ECMA-404, qui lui est réservé. Les deux normes, ce RFC et la norme ECMA, sont écrites de manière différente mais, en théorie, doivent aboutir au même résultat. ECMA et l'IETF sont censés travailler ensemble pour résoudre les incohérences (aucune des deux organisations n'a, officiellement, le dernier mot).

Contrairement à JavaScript, JSON n'est pas un langage de programmation, seulement un langage de description de données, et il ne peut donc pas servir de véhicule pour du code méchant (sauf si on fait des bêtises comme de soumettre du texte JSON à eval(), cf. section 12 et erratum #3607 qui donne des détails sur cette vulnérabilité).

Voici un exemple, tiré du RFC, d'un objet exprimé en JSON :

  {
      "Image": {
          "Width":  800,
          "Height": 600,
          "Title":  "View from 15th Floor",
          "Thumbnail": {
              "Url":    "http://www.example.com/image/481989943",
              "Height": 125,
              "Width":  "100"
          },
          "IDs": [116, 943, 234, 38793]
        }
   }

Les détails de syntaxe sont dans la section 2 du RFC. Cet objet d'exemple a un seul champ, Image, qui est un autre objet (entre { et }) et qui a plusieurs champs. (Les objets sont appelés dictionnaires ou maps dans d'autres langages.) L'ordre des éléments de l'objet n'est pas significatif (certains analyseurs JSON le conservent, d'autres pas). Un de ces champs, IDs, a pour valeur un tableau (entre [ et ]). Les éléments d'un tableau ne sont pas forcément du même type (section 5). Un texte JSON n'est pas forcément un objet ou un tableau, par exemple  :

"Hello world!"

est un texte JSON légal (composé d'une chaîne de caractères en tout et pour tout). Une des conséquences est qu'un lecteur de JSON qui lit au fil de l'eau peut ne pas savoir si le texte est fini ou pas (il ne suffit pas de compter les crochets et accolades). À part les objets, les tableaux et les chaînes de caractères, un texte JSON peut être un nombre, ou bien un littéral, false, true ou null.

Et quel encodage utiliser pour les textes JSON (section 8) ? Le RFC 4627 était presque muet à ce sujet. Cette question est désormais plus développée. Le jeu de caractères est toujours Unicode et l'encodage est obligatoirement UTF-8 dès qu'on envoie du JSON par le réseau (bien des mises en œuvre de JSON ne peuvent en lire aucun autre). Les textes JSON transmis par le réseau ne doivent pas utiliser de BOM.

Lorsqu'on envoie du JSON par le réseau, le type MIME à utiliser est application/json.

Autre problème classique d'Unicode, la comparaison de chaînes de caractères. Ces comparaisons doivent se faire selon les caractères Unicode et pas selon les octets (il y a plusieurs façons de représenter la même chaîne de caractères, par exemple foo*bar et foo\u002Abar sont la même chaîne).

JSON est donc un format simple, il n'a même pas la possibilité de commentaires dans le fichier... Voir sur ce sujet une intéressante compilation.

Le premier RFC décrivant JSON était le RFC 4627, remplacé ensuite par le RFC 7159. Quels changements apporte cette troisième révision (annexe A) ? Elle corrige quelques erreurs, résout quelques incohérences avec le texte ECMA, et donne des avis pratiques aux programmeurs. Les principaux changements :

Voici un exemple d'un programme Python pour écrire un objet Python en JSON (on notera que la syntaxe de Python et celle de JavaScript sont très proches) :

import json

objekt = {u'Image': {u'Width': 800,
                     u'Title': u'View from Smith\'s, 15th Floor, "Nice"',
                     u'Thumbnail': {u'Url':
                                    u'http://www.example.com/image/481989943',
                                    u'Width': u'100', u'Height': 125},
                     u'IDs': [116, 943, 234, 38793],
                     u'Height': 600}} # Example from RFC 4627, lightly modified

print(json.dumps(objekt))

Et un programme pour lire du JSON et le charger dans un objet Python :

import json

# One backslash for Python, one for JSON
objekt = json.loads("""
{
      "Image": {
          "Width":  800,
          "Height": 600,
          "Title":  "View from Smith's, 15th Floor, \\\"Nice\\\"", 
          "Thumbnail": {
              "Url":    "http://www.example.com/image/481989943",
              "Height": 125,
              "Width":  "100"
          },
          "IDs": [116, 943, 234, 38793]
        }
   }
""") # Example from RFC 4267, lightly modified

print(objekt)
print("")
print(objekt["Image"]["Title"])

Le code ci-dessus est très simple car Python (comme Perl ou Ruby ou, bien sûr, JavaScript) a un typage complètement dynamique. Dans les langages où le typage est plus statique, c'est moins facile et on devra souvent utiliser des méthodes dont certains programmeurs se méfient, comme des conversions de types à l'exécution. Si vous voulez le faire en Go, il existe un bon article d'introduction au paquetage standard json. Un exemple en Go figure plus loin, pour analyser la liste des stations de la RATP.

Pour Java, qui a le même « problème » que Go, il existe une quantité impressionnante de bibliothèques différentes pour faire du JSON (on trouve en ligne plusieurs tentatives de comparaison). J'ai utilisé JSON Simple. Lire un texte JSON ressemble à :

import org.json.simple.*;
...
Object obj=JSONValue.parse(args[0]);
if (obj == null) { // May be use JSONParser instead, it raises an exception when there is a problem
	    System.err.println("Invalid JSON text");
	    System.exit(1);
} else {
	    System.out.println(obj);
}

JSONObject obj2=(JSONObject)obj; // java.lang.ClassCastException if not a JSON object
System.out.println(obj2.get("foo")); // Displays member named "foo"

Et le produire :

JSONObject obj3=new JSONObject();
obj3.put("name","foo");
obj3.put("num",new Integer(100));
obj3.put("balance",new Double(1000.21));
obj3.put("is_vip",new Boolean(true));

Voyons maintenant des exemples réels avec divers outils de traitement de JSON. D'abord, les données issues du service de vélos en libre-service Vélo'v. C'est un gros JSON qui contient toutes les données du système. Nous allons programmer en Haskell un programme qui affiche le nombre de vélos libres et le nombre de places disponibles. Il existe plusieurs bibliothèques pour faire du JSON en Haskell mais Aeson semble la plus utilisée. Haskell est un langage statiquement typé, ce qui est loin d'être idéal pour JSON. Il faut déclarer des types correspondant aux structures JSON :

     
data Velov =
  Velov {values :: [Station]} deriving Show
  
instance FromJSON Velov where
 parseJSON (Object v) =
    Velov <$> (v .: "values") 

data Station =
  Station {stands :: Integer,
           bikes :: Integer,
           available :: Integer} deriving Show

data Values = Values [Station]

   

Mais ça ne marche pas : les nombres dans le fichier JSON ont été représentés comme des chaînes de caractères ! (Cela illustre un problème fréquent dans le monde de JSON et de l'open data : les données sont de qualité technique très variable.) On doit donc les déclarer en String :

data Station =
  Station {stands :: String,
           bikes :: String,
           available :: String} deriving Show
   

Autre problème, les données contiennent parfois la chaîne de caractères None. Il faudra donc filtrer avec la fonction Haskell filter. La fonction importante filtre les données, les convertit en entier, et en fait la somme grâce à foldl :

sumArray a =
  show (foldl (+) 0 (map Main.toInteger (filter (\i -> i /= "None") a)))
   

Le programme complet est velov.hs. Une fois compilé, testons-le :

% curl -s https://download.data.grandlyon.com/ws/rdata/jcd_jcdecaux.jcdvelov/all.json | ./velov   
"Stands: 6773"
"Bikes: 2838"
"Available: 3653"
   

Je n'ai pas utilisé les dates contenues dans ce fichier mais on peut noter que, si elles sont exprimées en ISO 8601 (ce n'est hélas pas souvent le cas), c'est sans indication du fuseau horaire (celui en vigueur à Lyon, peut-on supposer).

Un autre exemple de mauvais fichier JSON est donné par Le Monde avec la base des députés français. Le fichier est du JavaScript, pas du JSON (il commence par une déclaration JavaScript var datadep = {…) et il contient plusieurs erreurs de syntaxe (des apostrophes qui n'auraient pas dû être échappées).

Voyons maintenant un traitement avec le programme spécialisé dans JSON, jq. On va servir du service de tests TLS https://tls.imirhil.fr/, dont les résultats sont consultables avec un navigateur Web, mais également téléchargeables en JSON. Par exemple, https://tls.imirhil.fr/https/www.bortzmeyer.org.json donne accès aux résultats des tests pour la version HTTPS de ce blog :

% curl -s https://tls.imirhil.fr/https/www.bortzmeyer.org.json| jq '.date'    
"2017-07-23T14:10:25.760Z"
   

Notons qu'au moins une clé d'un objet JSON n'est pas nommée uniquement avec des lettres et chiffres, la clé $oid. jq n'aime pas cela :

     
% curl -s https://tls.imirhil.fr/https/www.bortzmeyer.org.json| jq '._id.$oid'
jq: error: syntax error, unexpected '$', expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at <top-level>, line 1:
._id.$oid     
jq: 1 compile error

   

Il faut mettre cette clé entre guillemets :

   
% curl -s https://tls.imirhil.fr/https/bortzmeyer.org.json| jq '."_id"."$oid"'                                                                               
"596cb76c2525939a3b34120f"

Toujours avec jq, les données de la Deutsche Bahn, en http://data.deutschebahn.com/dataset/data-streckennetz. C'est du GeoJSON (RFC 7946), un profil de JSON. Ici, on cherche la gare de Ratisbonne :

%  jq '.features | map(select(.properties.geographicalName == "Regensburg Hbf"))' railwayStationNodes.geojson
[
  {
    "type": "Feature",
    "geometry": {
      "type": "Point",
      "coordinates": [
        12.09966625451,
        49.011754555481
      ]
    },
    "properties": {
      "id": "SNode-1492185",
      "formOfNode": "railwayStop",
      "railwayStationCode": "NRH",
      "geographicalName": "Regensburg Hbf",
...
   

Toujours avec jq, on peut s'intéresser aux données officielles états-uniennes en https://catalog.data.gov/dataset?res_format=JSON. Prenons les données sur la délinquance à Los Angeles (j'ai bien dit délinquance et pas criminalité, celui qui traduit crime par crime ne connait pas l'anglais, ni le droit). https://data.lacity.org/api/views/y8tr-7khq/rows.json?accessType=DOWNLOAD est un très gros fichier (805 Mo) et jq n'y arrive pas :

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

Beaucoup de programmes qui traitent le JSON ont ce problème (un script Python produit un MemoryError) : ils chargent tout en mémoire et ne peuvent donc pas traiter des données de grande taille. Il faut donc éviter de produire de trop gros fichiers JSON.

Si vous voulez voir un vrai exemple en Python, il y a mon article sur le traitement de la base des codes postaux. Cette base peut évidemment aussi être examinée avec jq. Et c'est l'occasion de voir du GeoJSON :

% jq '.features | map(select(.properties.nom_de_la_commune == "LE TRAIT"))' laposte_hexasmal.geojson 
[
  {
    "type": "Feature",
    "geometry": {
      "type": "Point",
      "coordinates": [
        0.820017087099,
        49.4836816397
      ]
    },
    "properties": {
      "nom_de_la_commune": "LE TRAIT",
      "libell_d_acheminement": "LE TRAIT",
      "code_postal": "76580",
      "coordonnees_gps": [
        49.4836816397,
        0.820017087099
      ],
      "code_commune_insee": "76709"
    }
  }
]

J'avais promis plus haut un exemple écrit en Go. On va utiliser la liste des positions géographiques des stations RATP, en https://data.ratp.fr/explore/dataset/positions-geographiques-des-stations-du-reseau-ratp/table/?disjunctive.stop_name&disjunctive.code_postal&disjunctive.departement. Le programme Go read-ratp.go va afficher le nombre de stations et la liste :

% ./read-ratp < positions-geographiques-des-stations-du-reseau-ratp.json
26560 stations
Achères-Ville
Alésia
Concorde
...

Comme déjà indiqué, c'est plus délicat en Go que dans un langage très dynamique comme Python. Il faut construire à l'avance des structures de données :

  
type StationFields struct {
	Fields Station
}

type Station struct {
	Stop_Id   int
	Stop_Name string
}
   

Et toute violation du « schéma » des données par le fichier JSON (quelque chose qui arrive souvent dans la nature) plantera le programme.

Si on veut beaucoup de fichiers JSON, le service de données ouvertes officielles data.gouv.fr permet de sélectionner des données par format. Ainsi, https://www.data.gouv.fr/fr/datasets/?format=JSON donnera tous les fichiers en JSON. Prenons au hasard les frayères du centre de la France, https://www.data.gouv.fr/fr/datasets/points-de-frayere-des-especes-de-linventaire-frayeres-des-regions-centre-et-poitou-charentes/. Il est encodé en ISO-8859-1, ce qui est explicitement interdit par le RFC. Bref, il faut encore rappeler qu'on trouve de tout dans le monde JSON et que l'analyse de fichiers réalisés par d'autres amène parfois des surprises.

On peut aussi traiter du JSON dans PostgreSQL. Bien sûr, il est toujours possible (et sans doute parfois plus avantageux) d'analyser le JSON avec une des bibliothèques présentées plus haut, et de mettre les données dans une base PostgreSQL. Mais on peut aussi mettre le JSON directement dans PostgreSQL et ce SGBD fournit un type de données JSON et quelques fonctions permettant de l'analyser. Pour les données, on va utiliser les centres de santé en Bolivie, en http://geo.gob.bo/geoserver/web/?wicket:bookmarkablePage=:org.geoserver.web.demo.MapPreviewPage. On crée la table :

CREATE TABLE centers (
 ID serial NOT NULL PRIMARY KEY,
 info json NOT NULL
);
    

Si on importe le fichier JSON bêtement dans PostgreSQL (psql -c "copy centers(info) from stdin" mydb < centro-salud.json), on récupère un seul enregistrement. Il faut donc éclater le fichier JSON en plusieurs lignes. On peut utiliser les extensions à SQL de PostgreSQL pour cela, mais j'ai préféré me servir de jq :

%  jq --compact-output '.features | .[]' centro-salud.json |  psql -c "copy centers(info) from stdin" mydb
COPY 50
    

On peut alors faire des requêtes dans le JSON, avec l'opérateur ->. Ici, le nom des centres (en jq, on aurait écrit .properties.nombre) :

mydb=> SELECT info->'properties'->'nombre' AS Nom FROM centers;
                   nom                    
------------------------------------------
 "P.S. ARABATE"
 "INSTITUTO PSICOPEDAGOGICO"
 "HOSPITAL GINECO OBSTETRICO"
 "HOSPITAL GASTROENTEROLOGICO"
 "C.S. VILLA ROSARIO EL TEJAR"
 "C.S. BARRIO JAPON"
 "C.S. SAN ANTONIO ALTO (CHQ)"
 "C.S. SAN JOSE (CHQ)"
 "C.S. SAN ROQUE"
...
    

Bon, sinon, JSON dispose d'une page Web officielle, où vous trouverez plein d'informations. Pour tester dynamiquement vos textes JSON, il y a ce service.


Téléchargez le RFC 8259


L'article seul

Fiche de lecture : Les dix millénaires oubliés qui ont fait l'histoire

Auteur(s) du livre : Jean-Paul Demoule
Éditeur : Fayard
978-2-213-67757-6
Publié en 2017
Première rédaction de cet article le 10 décembre 2017


Quand on parle de la préhistoire, tout le monde voit ce que c'est : des types vêtus de peaux de bêtes partant chercher du feu ou chassant le mammouth, ou encore inventant plein de choses. Idem pour l'histoire, on a plein de livres et de films sur tous les aspects de l'histoire, depuis qu'on a inventé l'écriture, jusqu'à Stéphane Bern et Franck Ferrand. Mais entre les deux ? Entre les deux, dit l'auteur, il y a « dix millénaires zappés ». La période qui va en gros de 13 000 à 3 000 AEC : qu'en savons-nous ?

Le livre fait le tour des innovations qui ont marqué cette période un peu oubliée : l'agriculture, bien sûr, les villages, les outils en métal, mais aussi la religion organisée. Il y avait bien sûr des croyances depuis longtemps mais, pendant ces dix millénaires, on commence à en voir des manifestations partout, y compris au prix d'efforts colossaux, inimaginables quand on était chasseur-cueilleur. Et cela ne s'arrête pas là. C'est aussi pendant cette période, dix mille ans avant Macron, qu'on invente les inégalités. Avant, il y avait sans doute des chefs. Mais l'examen des tombes ne permettait pas de voir des vraies inégalités. Pendant les dix millénaires, on voit au contraire apparaitre la distinction entre ceux qu'on enterre simplement, et ceux dont le tombeau recèle des richesses qui feront la joie des archéologues. L'auteur examine comment des gens qui vivaient à peu près libres ont pu accepter cet asservissement, et note que cela n'a pas été un processus linéaire, mais qu'il y a eu de nombreux retours à des enterrements égalitaires (suite à des révoltes contre les chefs ?)

Mais la civilisation ne s'est pas arrêtée à la religion et aux chefs. On a aussi inventé la guerre. Oui, bien sûr, il y avait de la violence avant. Mais des armées entières s'affrontant, alors que la plupart des soldats n'avaient aucune raison personnelle de se battre, ça date aussi de cette époque. (L'Europe en dehors de la Grèce était en retard sur la voie de la civilisation, et n'a donc connu sa première grande bataille que vers 1 200 AEC.) Effet amusant de la guerre, c'est aussi pendant cette période que le cannibalisme est devenu tabou…

Et la domination masculine ? A-t-elle toujours existé ? Et, si non, a-t-elle été inventée pendant cette période ? Contrairement aux batailles, cela ne laisse pas forcément beaucoup de traces analysables. Là, on est forcément davantage dans la spéculation, comme les théories de Bachofen, qui cherchait à tout prix à prouver que la domination masculine n'est pas universelle (alors qu'apparemment, elle l'est).

Et je vous laisse découvrir dans ce livre les autres étonnantes innovations de l'époque.


L'article seul

RFC 8270: Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits

Date de publication du RFC : Décembre 2017
Auteur(s) du RFC : L. Velvindron (Hackers.mu), M. Baushke (Juniper Networks)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF curdle
Première rédaction de cet article le 6 décembre 2017


Un RFC de moins de quatre pages, boilerplate administratif inclus, pour passer la taille minimum des modules des groupes Diffie-Hellman utilisés par SSH, de 1 024 bits à 2 048.

L'échange Diffie-Hellman dans SSH est décrit dans le RFC 4419, que notre nouveau RFC met à jour. C'est dans le RFC 4419 (sa section 3) que se trouvait la recommandation d'accepter au moins 1 024 bits pour le module du groupe. Or, cette taille est bien faible face aux attaques modernes comme Logjam.

Voilà, c'est tout, on remplace « minimum 1 024 bits » par « minimum 2 048 » et on peut continuer à utiliser SSH. Si vous êtes utilisateur d'OpenSSH, la commande de génération de clés, ssh-keygen, peut également générer ces modules (cf. la section Moduli generation dans le manuel.) Les versions un peu anciennes ne vous empêchent pas de faire des modules bien trop petits. Ainsi, sur une version 7.2 :

% ssh-keygen -G moduli-512.candidates -b 512
Fri Oct 20 20:13:49 2017 Sieve next 4177920 plus 511-bit
Fri Oct 20 20:14:51 2017 Sieved with 203277289 small primes in 62 seconds
Fri Oct 20 20:14:51 2017 Found 3472 candidates

% ssh-keygen -G moduli-256.candidates -b 256
Too few bits: 256 < 512
modulus candidate generation failed
    

Le RGS recommande quant à lui 3 072 bits minimum (la règle exacte est « RègleLogp-1. La taille minimale de modules premiers est de 2048 bits pour une utilisation ne devant pas dépasser l’année 2030. RègleLogp-2. Pour une utilisation au delà de 2030, la taille minimale de modules premiers est de 3072 bits. »)

Enfin, la modification d'OpenSSH pour se conformer à ce RFC est juste un changement de la définition de DH_GRP_MIN.


Téléchargez le RFC 8270


L'article seul

RFC 8273: Unique IPv6 Prefix Per Host

Date de publication du RFC : Décembre 2017
Auteur(s) du RFC : J. Brzozowski (Comcast Cable), G. Van De Velde (Nokia)
Pour information
Réalisé dans le cadre du groupe de travail IETF v6ops
Première rédaction de cet article le 5 décembre 2017


Ce court RFC explique comment (et pourquoi) attribuer un préfixe IPv6 unique à chaque machine, même quand le média réseau où elle est connectée est partagé avec d'autres machines.

Ce RFC s'adresse aux gens qui gèrent un grand réseau de couche 2, partagé par de nombreux abonnés. Un exemple est celui d'un serveur dédié connecté à un Ethernet partagé avec les serveurs d'autres clients. Un autre exemple est celui d'une connexion WiFi dans un congrès ou un café. Dans les deux cas, la pratique sans doute la plus courante aujourd'hui est de donner une seule adresse IPv6 à la machine (ou, ce qui revient au même, un préfixe de 128 bits). C'est cette pratique que veut changer ce RFC. Le but est de mieux isoler les clients les uns des autres, et de bien pouvoir gérer les abonnements et leur utilisation. (Justement le problème de Comcast, dont un des auteurs du RFC est un employé, cf. section 1.) Les clients ne se connaissent en effet pas les autres et il est important que les actions d'un client ne puissent pas trop affecter les autres (et qu'on puisse attribuer les actions à un client précis, pour le cas où ces actions soient illégales). En outre, certaines options d'abonnement sont « par client » (section 3, qui cite par exemple le contrôle parental, ou la qualité de service, qui peut être plus faible pour ceux qui ne paient pas le tarif « gold ».)

Si chaque client a un préfixe IPv6 à lui (au lieu d'une seule adresse IP), toute communication entre clients passera forcément par le routeur géré par l'opérateur, qui pourra ainsi mieux savoir ce qui se passe, et le contrôler. (Les lecteurs férus de routage ont noté que le client, s'il est administrateur de sa machine, peut toujours changer les tables de routage, mais cela n'affectera que le trafic aller, le retour passera toujours par le routeur. De toute façon, je soupçonne que la technique décrite dans ce RFC ne marche que si le réseau donne un coup de main, pour isoler les participants.)

Le RFC affirme que cela protègera contre des attaques comme l'épuisement de cache Neighbor Discovery, les redirections malveillantes faites avec ICMP ou les RAcailles (RFC 6104). Cela éviterait de devoir déployer des contre-mesures comme le RA Guard (RFC 6105). Là aussi, il me semble personnellement que ça n'est complètement vrai que si l'attaquant n'est pas administrateur sur sa machine. Ou alors, il faut que le réseau ne soit pas complètement partagé, qu'il y ait un mécanisme de compartimentage.

Les mécanismes décrits ici supposent que la machine du client utilise SLAAC (RFC 4862) pour obtenir une adresse IP. Cette obtention peut aussi passer par DHCP (RFC 3315) mais c'est plus rare, peu de clients étant capable de demander une adresse en DHCP (RFC 7934).

La section 4 du RFC décrit comment le client obtient ce préfixe. Il va envoyer un message RS (Router Solicitation, voir le RFC 4861, section 3) et écouter les réponses, qui lui diront son adresse IP mais aussi d'autres informations comme les résolveurs DNS à utiliser (voir RFC 8106). Pas de changement côté client, donc (ce qui aurait rendu ce mécanisme difficile à déployer). Par contre, côté « serveur », il y a de légers changements. Le routeur qui reçoit les RS et génère des RA (Router Advertisement), qu'ils aient été sollicités ou pas, va devoir les envoyer uniquement à une machine (puisque chaque client a un préfixe différent : il ne faut donc pas diffuser bêtement). Comme le RFC 4861 (sections 6.2.4 et 6.2.6) impose que l'adresse IP de destination soit ff02::1 (« tous les nœuds IPv6 »), l'astuce est d'utiliser comme adresse MAC, non pas l'adresse multicast habituelle, mais une adresse unicast (RFC 6085). Ainsi, chaque client ne recevra que son préfixe.

Ce RA contient le préfixe que l'opérateur alloue à ce client particulier. Les options du RA (RFC 4861, section 4.2) sont :

  • Bit M à zéro (ce qui veut dire « pas d'adresse via DHCP »),
  • Bit O à un (ce qui veut dire « d'autres informations sont disponibles par DHCP, par exemple le serveur NTP à utiliser »),
  • Bit A du préfixe (RFC 4861, section 4.6.2) mis à un (ce qui veut dire « tu es autorisé à te configurer une adresse dans ce préfixe »),
  • Bit L du préfixe (RFC 4861, section 4.6.2) mis à zéro (ce qui veut dire « ce préfixe n'est pas forcément sur le lien où tu te trouves, ne suppose rien, sois gentil, passe par le routeur »).

Le bit A étant mis à un, la machine qui a obtenu le préfixe peut s'attribuer une adresse IP à l'intérieur de ce préfixe, , avec SLAAC, comme indiqué dans le RFC 4862. Elle doit suivre la procédure DAD (Duplicate Address Detection, RFC 4862, section 5.4) pour vérifier que l'adresse IP en question n'est pas déjà utilisée.

Voilà, l'essentiel de ce RFC était là. La section 5 concerne quelques détails pratiques, par exemple ce que peut faire la machine client si elle héberge plusieurs machines virtuelles ou containers (en gros, elle alloue leurs adresses en utilisant le préfixe reçu).

Ce mécanisme de préfixe IP spécifique à chaque client de l'opérateur n'est pas sans poser des questions liées à la vie privée, comme l'explique la section 7 du RFC. (Rappelez-vous la section 1, qui disait qu'un des buts de cette technique était de satisfaire aux « obligations légales », autrement dit de pouvoir suivre à la trace ses utilisateurs.) Bien sûr, la machine cliente peut utiliser le système du RFC 4941, mais, ici, il aurait peu d'impact. Même avec un identificateur d'interface temporaire et imprévisible, le préfixe resterait, et identifierait parfaitement le client. Le RFC mentionne (mais sans l'exiger) qu'on peut limiter les dégâts en changeant le préfixe de temps en temps.


Téléchargez le RFC 8273


L'article seul

Conserver sa clé avec Let's Encrypt, certbot et dehydrated

Première rédaction de cet article le 2 décembre 2017


Beaucoup de gens utilisent désormais l'AC Let's Encrypt. Ce n'est pas la première autorité de certification qui permet d'avoir un certificat en faisant tout en ligne, ni la première gratuite, mais elle est néanmoins très utilisée (au point de devenir un nouveau SPOF de l'Internet). Par défaut, les outils Let's Encrypt comme certbot créent une nouvelle clé quand le certificat est renouvelé. Dans quels cas est-ce gênant et comment éviter cela ?

Un petit rappel sur les certificats : un certificat, c'est tout bêtement une clé publique, avec quelques métadonnées dont les plus importantes sont la signature de l'AC et la date d'expiration (qui, avec Let's Encrypt, est très rapprochée). Renouveler un certificat, c'est demander une nouvelle signature à l'AC. Si la clé n'est pas trop ancienne, ou n'a apparemment pas été compromise, il n'est pas nécessaire de la changer.

Mais les outils existants le font quand même systématiquement (c'est un choix des outils, comme certbot ou dehydrated, ce n'est pas une obligation du protocole ACME, ni de l'AC Let's Encrypt). Cette décision a probablement été prise pour garantir que la clé soit renouvelée de temps en temps (après tout, il est raisonnable de supposer que, tôt ou tard, elle sera compromise, et ce ne sera pas forcément détecté par le propriétaire).

Et pourquoi est-ce gênant de changer de clé à chaque renouvellement (donc tous les trois mois avec Let's Encrypt) ? Cela ne pose pas de problème pour l'utilisation habituelle d'un serveur HTTPS. Mais c'est ennuyeux si on utilise des techniques de sécurité fondées sur un épinglage de la clé, c'est-à-dire une authentification de la clé publique utilisée. Ces techniques permettent de résoudre une grosse faille de X.509, le fait que n'importe quelle AC, même si vous n'en êtes pas client, puisse émettre un certificat pour n'importe quel domaine. Parmi ces techniques de sécurité :

Si on utilise l'outil certbot, qui est celui officiellement recommandé par Let's Encrypt, la méthode normale d'utilisation est, la première fois :

  
% sudo certbot certonly --webroot --webroot-path /var/lib/letsencrypt -d www.example.com 
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.example.com
Using the webroot path /var/lib/letsencrypt for all unmatched domains.
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
...

Let's Encrypt a testé la présence du défi sur le serveur, on le voit dans le journal du serveur HTTP :

2600:3000:2710:200::1d - - [13/Sep/2017:16:08:46 +0000] "GET /.well-known/acme-challenge/2IlM1PbP9QZlAA22xvE4Bz5ivJi5nsB5MHz52uY8xT8 HTTP/1.1" 200 532 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

On va alors créer l'enregistrement TLSA (DANE) :

% tlsa --create --selector 1  www.example.com
Got a certificate with Subject: /CN=www.example.com
_443._tcp.www.example.com. IN TLSA 3 1 1 f582936844ec355cfdfe8d9d1a42e9565940602c71c7abd2c36c732daa64b9db
Got a certificate with Subject: /CN=www.example.com
_443._tcp.www.example.com. IN TLSA 3 1 1 f582936844ec355cfdfe8d9d1a42e9565940602c71c7abd2c36c732daa64b9db

(L'option --selector 1 est pour faire apparaitre dans l'enregistrement TLSA la clé publique seulement et non pas tout le certificat, ce que ferait le sélecteur par défaut, 0. C'est expliqué plus en détail plus loin.) À ce stade, on a un certificat Let's Encrypt, un enregistrement DANE qui correspond et tout le monde est heureux :

% tlsa --verify --resolvconf="" www.example.com 
SUCCESS (Usage 3 [DANE-EE]): Certificate offered by the server matches the TLSA record (x.y.z.t)
   

Maintenant, si on renouvelle le certificat quelques mois plus tard :

    
% certbot --quiet  --webroot --webroot-path /usr/share/nginx/local-html renew

Cela change la clé. Regardez avec OpenSSL :

# Avant  
 % openssl x509 -pubkey -in /etc/letsencrypt/live/www.example.com/fullchain.pem 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsUd3mG5QK6EdTtYh0oLJ
nkIovYkWXV8QLQMvAthzURbyeIlQ8CXeTNCT6odh/VVyMn49IwkRJl6B7YNhYiRz
pbmIxxzceAhKNAg6TF/QylHa1HWvHPniZF02NJAXCxMO5Y8EZ7n0s0cGz4XD5PGA
XctV6ovA3fR8b2bk9t5N+UHklWvIOT7x0nVXWmWmrXzG0LX/P4+utZJjRR6Kf5/H
9GDXprklFCbdCTBkhyPBgdiJDnqzdb6hB1aBEsAMd/Cplj9+JKtu2/8Pq6MOtQeu
364N+RKcNt4seEr6uMOlRXzWAfOHI51XktJT64in1OHyoeRMV9dOWOLWIC2KAlI2
jwIDAQAB
-----END PUBLIC KEY-----

# Après
% openssl x509 -pubkey -in /etc/letsencrypt/live/www.example.com/fullchain.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6MF8Dw3JQ58n8B/GvWYI
Vd+CG7PNFA+Ke7B+f9WkzEIUPzAHq7qk1v7dOitD3WsRKndJDPxZAq7JrgOiF/0y
4726HhYR1bXOTziAbk0HzR+HwECo1vz26fqPnNpZ3M46PQFQU9uq2pgHtBwCVMQ+
Hi1pYKnB2+ITl11DBLacSHP7WZZGHXbEqW5Cc710m6aTt18L+OgqxuQSgV+khh+W
qWqd2bLq32actLEVmfR4uWX7fh/g6I7/p3ohY7Ax4WC30RfWZk3vLyNc/8R0/67c
bVIYWmkDhgXy6UlrV2ZgIO2K8oKiJBMHjnaueHIfu1ktubqM1/u1yLKwXW16UAxm
5QIDAQAB
-----END PUBLIC KEY-----

À partir de là, l'enregistrement DANE ne correspond plus, la clé copiée à l'extérieur n'est plus la clé utilisée.

Avec certbot, la solution est de ne pas laisser le client ACME choisir le certificat, mais de fabriquer un CSR et de lui indiquer de l'utiliser systématiquement (cf. la documentation officielle) :

% certbot certonly --webroot -w /usr/share/nginx/local-html -d dns-resolver.yeti.eu.org --csr /etc/letsencrypt-local/yeti-resolver.csr --cert-path /etc/letsencrypt-local/tmp.pem 
No handlers could be found for logger "certbot.crypto_util"
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Performing the following challenges:
http-01 challenge for dns-resolver.yeti.eu.org
Using the webroot path /var/lib/letsencrypt for all unmatched domains.
Waiting for verification...
Cleaning up challenges
Server issued certificate; certificate written to /etc/letsencrypt-local/dns-resolver.yeti.eu.org.pem

Comment on avait fabriqué un CSR ? OpenSSL le permet. Faisons-en un joli, utilisant la cryptographie à courbes elliptiques :

%  openssl ecparam -out yeti-resolver.pem -name prime256v1 -genkey

%  openssl req -new -key yeti-resolver.pem  -nodes -days 1000 -out yeti-resolver.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
...
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Dahu
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:dns-resolver.yeti.eu.org
Email Address []:yeti@eu.org

Avec ce CSR, et en appelant certbot depuis cron avec les options indiquées plus haut, indiquant le même CSR (certbot certonly --webroot -w /usr/share/nginx/local-html -d dns-resolver.yeti.eu.org --csr /etc/letsencrypt-local/yeti-resolver.csr --cert-path /etc/letsencrypt-local/tmp.pem), la clé reste constante, et DANE et HPKP fonctionnent. Petit inconvénient : avec ces options, certbot renouvelle le certificat à chaque fois, même quand ça n'est pas nécessaire.

Et si on utilise dehydrated au lieu de certbot, comme client ACME ? Là, c'est plus simple, on met dans le fichier de configuration /etc/dehydrated/config l'option :

PRIVATE_KEY_RENEW="no"

Et cela suffit. Ceci dit, dehydrated a un gros inconvénient, il est bavard. Quand on le lance depuis cron, il affiche systématiquement plusieurs lignes, même s'il n'a rien à dire.

Pour revenir à la question du choix du sélecteur DANE (RFC 6698 et RFC 7218), il faut noter que tout renouvellement change le certificat (puisqu'il modifie au moins la date d'expiration). Il ne faut donc pas utiliser le sélecteur 0 « Cert » (qui publie le condensat du certificat entier dans l'enregistrement TLSA) mais le sélecteur 1 « SPKI » (qui ne met que le condensat de la clé). Le problème existe avec toutes les AC mais est plus aigu pour Let's Encrypt où on renouvelle souvent. L'annexe A.1.2 du RFC 6698 l'explique bien.

Enfin, un avertissement de sécurité : avec les méthodes indiquées ici, le client ACME ne change plus de clé du tout. C'est donc désormais à vous de penser à créer une nouvelle clé de temps en temps, pour suivre les progrès de la cryptanalyse.

Si vous voulez des exemples concrets, dns-resolver.yeti.eu.org (uniquement en IPv6) utilise certbot, alors que dns.bortzmeyer.org et mercredifiction.bortzmeyer.org utilisent dehydrated. Prenons dns.bortzmeyer.org. Son enregistrement TLSA :

% dig TLSA _443._tcp.dns.bortzmeyer.org
...
;; ANSWER SECTION:
_443._tcp.dns.bortzmeyer.org. 80968 IN TLSA 1 1 1 (
				C05BF52EFAB00EF36AC6C8E1F96A25CC2A79CC714F77
				055DC3E8755208AAD0E4 )
				...
;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sat Dec 02 17:03:18 CET 2017
;; MSG SIZE  rcvd: 2311
   

Il déclare une contrainte sur le certificat du serveur (champ Certificate usage à 1, PKIX-EE), ne testant que la clé (champ Selector à 1), l'enregistrement est un condensat SHA-256 (champ Matching type à 1). On peut vérifier que l'enregistrement DANE est correct avec hash-slinger :

% tlsa --verify --resolvconf="" dns.bortzmeyer.org
SUCCESS (Usage 1 [PKIX-EE]): Certificate offered by the server matches the one mentioned in the TLSA record and chains to a valid CA certificate (204.62.14.153)
SUCCESS (Usage 1 [PKIX-EE]): Certificate offered by the server matches the one mentioned in the TLSA record and chains to a valid CA certificate (2605:4500:2:245b::42)
   

ou bien avec https://check.sidnlabs.nl/dane/.


L'article seul

Choisir son résolveur DNS, pas si facile

Première rédaction de cet article le 28 novembre 2017


Le 26 novembre 2017, à la Cité des Sciences à Paris, j'ai fait un exposé lors de l'Ubuntu Party sur la question des critères de choix d'un bon résolveur DNS. Les supports de cet exposé sont ici.

Les Ubuntu Party sont organisées par les bénévoles utilisateurs d'Ubuntu et comprennent une install party (des dizaines de personnes sont passées) et des exposés plus traditionnels. J'ai beaucoup aimé les deux exposés d'Elzen, le premier sur la piraterie et le piratage, et le second une « conférence dont vous êtes le héros », ainsi que l'exposé de Pamputt sur le remarquable Wiktionnaire, projet moins connu que Wikipédia mais tout aussi intéressant !

La question du choix d'un résolveur DNS fait toujours couler beaucoup d'encre sur les réseaux sociaux et a déjà fait l'objet de plusieurs articles sur ce blog, au sujet de Quad9, au sujet de Google Public DNS, à propos des résolveurs publics en général, de l'intérêt d'avoir son propre résolveur, de la protection de la vie privée (RFC 7626), de la censure, etc. Voici les transparents de l'exposé à l'Ubuntu Party :

L'exposé a été filmé mais je ne sais pas quand la vidéo sera en ligne.


L'article seul

Fiche de lecture : Artemis

Auteur(s) du livre : Andy Weir
Éditeur : Del Rey
9780091956943
Publié en 2017
Première rédaction de cet article le 21 novembre 2017


Le nouveau roman de l'auteur du Martien (oui, c'est nul de présenter un auteur par son bouquin précédent qui a eu du succès mais je n'avais pas d'autre idée). Il garde le côté « hard-tech », mais tend cette fois vers le polar.

Dans « Le Martien », le héros devait se débrouiller tout seul sur la planète Mars et l'un des principaux intérêts du roman était la description détaillée et correcte scientifiquement des différents problèmes qu'il affrontait, et de comment il les résolvait. Cette fois, ça se passe sur la Lune, et il s'agit de problèmes plus policiers, avec des méchants (très méchants), des complots et des sabotages (qui ont tout de suite un caractère plus dramatique quand il n'y a pas d'atmosphère dehors et qu'on ne peut donc pas sortir pour échapper au danger).

Comme le précédent, ce roman plaira aux geeks scientifiques. Ils y trouveront de la physique et de la chimie en quantité. Si vous le lisez en anglais, pensez à réviser la terminologie, notamment du monde de la soudure, qui joue un rôle essentiel dans le livre. « I wasn't sure what grade of steel [they] were made of, but most grades melt at or below 1450° C. So, just to be safe, my plate and stock rods were Grade 416 with a melting point of 1530° C. »

Mais les autres seront également ravis des personnages (surprenants), de la morale (curieuse), de l'intrigue (à rebondissements) et de l'(abondante) action. Mon fils, regardant par-dessus mon épaule, m'avait dit « c'est encore un livre où le héros affronte des épreuves, les surmonte puis pécho la jolie fille à la fin ? » Eh bien, non, ce n'est pas ça du tout, vous verrez en lisant.


L'article seul

RFC 8274: Incident Object Description Exchange Format Usage Guidance

Date de publication du RFC : Novembre 2017
Auteur(s) du RFC : P. Kampanakis (Cisco Systems), M. Suzuki (NICT)
Pour information
Réalisé dans le cadre du groupe de travail IETF mile
Première rédaction de cet article le 20 novembre 2017


Le format IODEF, dont la dernière version est décrite dans le RFC 7970, est un format structuré permettant l'échange de données sur des incidents de sécurité. Cela permet, par exemple, aux CSIRT de se transmettre des données automatiquement exploitables. Ces données peuvent être produites automatiquement (par exemple par un IDS, ou bien issues du remplissage manuel d'un formulaire). IODEF est riche, très riche, peut-être trop riche (certaines classes qu'il définit ne sont que rarement utilisées). Il peut donc être difficile de programmer des outils IODEF, et de les utiliser. (En pratique, il me semble qu'IODEF est peu utilisé.) Ce RFC, officiellement, est donc chargé d'aider ces professionnels, en expliquant les cas les plus courants et les plus importants, et en guidant programmeurs et utilisateurs.

Personnellement, je ne suis pas convaincu du résultat, ce RFC me semble plutôt un pot-pourri de diverses choses qui n'avaient pas été mises dans la norme.

La section 3 du RFC discute de l'utilisation de base d'IODEF. Reprenant la section 7.1 du RFC 7970, elle présente le document IODEF minimum, celui avec uniquement l'information obligatoire :

      
<?xml version="1.0" encoding="UTF-8"?>
<IODEF-Document version="2.00" xml:lang="en"
      xmlns="urn:ietf:params:xml:ns:iodef-2.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.iana.org/assignments/xml-registry/schema/iodef-2.0.xsd">
   <Incident purpose="reporting" restriction="private">
       <IncidentID name="csirt.example.com">492382</IncidentID>
       <GenerationTime>2015-07-18T09:00:00-05:00</GenerationTime>
       <Contact type="organization" role="creator">
         <Email>
           <EmailTo>contact@csirt.example.com</EmailTo> <!-- Pas
	   réellement obligatoire, mais le document serait vraiment
	   sans intérêt sans lui. -->
         </Email>
       </Contact>
     </Incident>
</IODEF-Document>

    

Un tel document, comportant une instance de la classe Incident, qui comprend elle-même une instance de la classe Contact, serait syntaxiquement correct mais n'aurait guère d'intérêt pratique. Des documents un peu plus réalistes figurent dans l'annexe B.

Le programmeur qui génère ou traite des fichiers IODEF n'a pas forcément à mettre en œuvre la totalité des classes. Il peut se contenter de ce qui est important pour son ou ses scénarios d'utilisation. Par exemple, si on travaille sur les dDoS, la classe Flow est la plus importante, puisqu'elle décrit le trafic de l'attaque. (L'annexe B.2 du RFC contient un fichier IODEF décrivant une attaque faite avec LOIC. Je l'ai copié ici dans ddos-iodef.xml.) De même, si on travaille sur le C&C d'un logiciel malveillant, les classes Flow et ServiceName sont cruciales. Bref, il faut analyser ce dont on a besoin.

La section 4 du RFC mentionne les extensions à IODEF. Si riche que soit ce format, on peut toujours avoir besoin d'autres informations et c'est pour cela qu'IODEF est extensible. Par exemple, le RFC 5901 décrit une extension à IODEF pour signaler des cas de hameçonnage. Évidemment, on ne doit définir une extension que s'il n'existe pas de moyen existant de stocker l'information dans de l'IODEF standard.

La section 4 rappelle aussi aux développeurs que, certes, IODEF bénéfice d'un mécanisme d'indication de la confidentialité (l'attribut restriction, qui se trouve dans les deux exemples que j'ai cité), mais qu'IODEF ne fournit aucun moyen technique de le faire respecter. Les documents IODEF étant souvent sensibles, puisqu'ils parlent de problèmes de sécurité, le programmeur qui réalise un système de traitement de fichiers IODEF doit donc mettre en œuvre des mesures pratiques de protection de la confidentialité (chiffrement des fichiers stockés, par exemple).

Questions mise en œuvre d'IODEF, le RFC 8134 détaille des programmes existants, indique où les récupérer quand ils sont accessibles en ligne, et analyse leurs caractéristiques. C'est le cas par exemple d'iodeflib.


Téléchargez le RFC 8274


L'article seul

Quad9, un résolveur DNS public, et avec sécurité

Première rédaction de cet article le 16 novembre 2017


Aujourd'hui a été annoncé la disponibilité du résolveur DNS Quad9 (prononcer « quoi de neuf » en français). C'est un résolveur DNS public, mais dont l'originalité est d'être accessible de manière sécurisée, avec TLS (RFC 7858). (There is also an english version of this article.)

Des résolveurs DNS publics, il y en a plein. Le plus connu est Google Public DNS mais il en existe beaucoup d'autres, avec des politiques et des caractéristiques techniques diverses. Le fait que tant d'utilisateurs se servent aveuglément de Google Public DNS, malgré l'énorme quantité de données que Google connait déjà sur nous est inquiétant. Mais il y aussi un problème technique, commun à la plupart des résolveurs publics : le lien avec eux n'est pas sécurisé. Cela permet les détournements, comme vu en Turquie, ainsi que la surveillance par un tiers.

Au contraire, le nouveau service Quad9, géré par l'organisme sans but lucratif bien connu PCH, qui gère une bonne partie de l'infrastructure du DNS, Quad9, donc, permet un accès par DNS sur TLS (RFC 7858). Cela permet d'éviter l'écoute par un tiers, et cela permet d'authentifier le résolveur (je n'ai pas encore testé ce point, Quad9 ne semble pas distribuer de manière authentifiée ses clés publiques).

Question politique, notons encore que Quad9 s'engage à ne pas stocker votre adresse IP. Et que leur résolveur est un résolveur menteur : il ne répond pas (délibérement) pour les noms de domaines considérés comme lié à des activités néfastes comme la distribution de logiciel malveillant. On peut avoir un résolveur non-menteur en utilisant d'autres adresses mais on perd DNSSEC et surtout Quad9 se met à utiliser alors l'indication du réseau du client (RFC 7871), une mauvaise pratique pour la vie privée. Espérons qu'on aura bientôt une adresse pour les réponses non-menteuses, avec DNSSEC et sans l'indication du réseau du client.

Bon, passons maintenant à la pratique, sur une machine Unix. L'adresse IPv4 de Quad9, comme son nom l'indique, est 9.9.9.9. Son adresse IPv6 est 2620:fe::fe (cf. la FAQ). D'abord, un accès classique en UDP en clair :

      
% dig +nodnssec @9.9.9.9 AAAA irtf.org   

; <<>> DiG 9.10.3-P4-Ubuntu <<>> +nodnssec @9.9.9.9 AAAA irtf.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11544
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;irtf.org.		IN AAAA

;; ANSWER SECTION:
irtf.org.		1325 IN	AAAA 2001:1900:3001:11::2c

;; Query time: 4 msec
;; SERVER: 9.9.9.9#53(9.9.9.9)
;; WHEN: Thu Nov 16 09:49:41 +08 2017
;; MSG SIZE  rcvd: 65

    

On y voit que Quad9 valide avec DNSSEC (la réponse a bien le bit AD - Authentic Data).

Si le domaine est sur la liste noire de Quad9 (merci à Xavier Claude pour avoir trouvé un nom), le résolveur répond NXDOMAIN (No Such Domain, ce domaine n'existe pas) :

    
%  dig @9.9.9.9  www.hjaoopoa.top

; <<>> DiG 9.10.3-P4-Debian <<>> @9.9.9.9 www.hjaoopoa.top
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 1143
;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;www.hjaoopoa.top.	IN A

;; Query time: 17 msec
;; SERVER: 9.9.9.9#53(9.9.9.9)
;; WHEN: Sat Nov 18 20:30:41 CET 2017
;; MSG SIZE  rcvd: 45

(Avec un résolveur non-menteur, on aurait eu le code de retour NOERROR et l'adresse IP 54.213.138.248.)

Maintenant, testons la nouveauté importante de ce service, DNS sur TLS (RFC 7858). C'est du TLS donc on peut y aller avec openssl :

% openssl s_client -connect \[2620:fe::fe\]:853 -showcerts
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = dns.quad9.net
verify return:1
---
Certificate chain
 0 s:/CN=dns.quad9.net
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
-----BEGIN CERTIFICATE-----
...
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
...
Server certificate
subject=/CN=dns.quad9.net
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
...
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
...
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
...

On voit que Quad9 répond bien en TLS, et a un certificat Let's Encrypt.

Testons ensuite avec un client DNS, le programme getdns_query distribué avec getdns :

      
% getdns_query @9.9.9.9 -s -l L www.afnic.fr AAAA
{
  "answer_type": GETDNS_NAMETYPE_DNS,
  "canonical_name": <bindata for lb01-1.nic.fr.>,
  "just_address_answers":
  [
    {
      "address_data": <bindata for 2001:67c:2218:30::24>,
      "address_type": <bindata of "IPv6">
    }
...

    

(Oui, getdns_query est très bavard.) L'option -l L lui dit d'utiliser DNS sur TLS.

On va d'ailleurs utiliser tshark pour vérifier qu'on est bien en TLS :

% tshark -n -i wlp2s0  -d tcp.port==853,ssl host 9.9.9.9 
Capturing on 'wlp2s0'
1 0.000000000 31.133.136.116 → 9.9.9.9      TCP 74 37874 → 853 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=233018174 TSecr=0 WS=128
2 0.002518390      9.9.9.9 → 31.133.136.116 TCP 74 853 → 37874 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=873811762 TSecr=233018174 WS=256
3 0.002551638 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=233018175 TSecr=873811762
4 0.002642065 31.133.136.116 → 9.9.9.9      SSL 371 Client Hello
5 0.022008585      9.9.9.9 → 31.133.136.116 TLSv1.2 1514 Server Hello
6 0.022042645 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=306 Ack=1449 Win=32128 Len=0 TSval=233018180 TSecr=873811781
7 0.022050371      9.9.9.9 → 31.133.136.116 TLSv1.2 108 [TCP Previous segment not captured] , Ignored Unknown Record
8 0.022054712 31.133.136.116 → 9.9.9.9      TCP 78 [TCP Window Update] 37874 → 853 [ACK] Seq=306 Ack=1449 Win=35072 Len=0 TSval=233018180 TSecr=873811781 SLE=2897 SRE=2939
9 0.022667110      9.9.9.9 → 31.133.136.116 TCP 1514 [TCP Out-Of-Order] 853 → 37874 [ACK] Seq=1449 Ack=306 Win=30208 Len=1448 TSval=873811781 TSecr=233018175
10 0.022679278 31.133.136.116 → 9.9.9.9      TCP 66 37874 → 853 [ACK] Seq=306 Ack=2939 Win=37888 Len=0 TSval=233018180 TSecr=873811781
11 0.023537602 31.133.136.116 → 9.9.9.9      TLSv1.2 192 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
12 0.037713598      9.9.9.9 → 31.133.136.116 TLSv1.2 117 Change Cipher Spec, Encrypted Handshake Message
13 0.037888417 31.133.136.116 → 9.9.9.9      TLSv1.2 225 Application Data
14 0.093441153      9.9.9.9 → 31.133.136.116 TCP 66 853 → 37874 [ACK] Seq=2990 Ack=591 Win=31232 Len=0 TSval=873811853 TSecr=233018184
15 0.742375719      9.9.9.9 → 31.133.136.116 TLSv1.2 178 Application Data
...

Le -d tcp.port==853,ssl était là pour dire à tshark d'interpréter ce qui passe sur le port 853 (celui de DNS-sur-TLS) comme étant du TLS. On voit bien le dialogue TLS mais évidemment pas les questions et réponses DNS puisque tout est chiffré.

Bien, maintenant que les tests se passent bien, comment utiliser Quad9 pour la vraie résolution de noms ? On va utiliser stubby pour parler à Quad9. Le fichier de configuration Stubby sera du genre:

listen_addresses:
  - 0::1@8053
# https://github.com/getdnsapi/getdns/issues/358

dns_transport_list:
  - GETDNS_TRANSPORT_TLS

upstream_recursive_servers:
# Quad9
   - address_data: 9.9.9.9
     tls_auth_name: "dns.quad9.net"
   - address_data: 2620:fe::fe
     tls_auth_name: "dns.quad9.net"
   

On indique à stubby d'écouter sur l'adresse locale ::1, port 8053, et de faire suivre les requêtes en DNS sur TLS à 9.9.9.9 ou 2620:fe::fe. On lance stubby :

% stubby
[12:28:10.942595] STUBBY: Read config from file /usr/local/etc/stubby/stubby.yml
[12:28:10.942842] STUBBY: Starting DAEMON....
   

Et on peut le tester, en utilisant dig pour interroger à l'adresse et au port indiqué :

   

% dig @::1 -p 8053 A www.catstuff.com 

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @::1 -p 8053 A www.catstuff.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20910
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 65535
;; QUESTION SECTION:
;www.catstuff.com.	IN A

;; ANSWER SECTION:
www.catstuff.com.	600 IN A 216.157.88.24

;; Query time: 974 msec
;; SERVER: ::1#8053(::1)
;; WHEN: Thu Nov 16 20:29:26 +08 2017
;; MSG SIZE  rcvd: 77

Et on peut vérifier avec tshark ou tcpdump que Stubby parle bien avec Quad9, et en utilisant TLS.

Si, à ce stade, vous obtenez une réponse DNS FORMERR (FORmat ERRor) au lieu du NOERROR qu'on voit ci-dessus, c'est à cause de cette bogue et il faut mettre à jour la bibliothèque getdns utilisée par Stubby.

Stubby a l'avantage de bien gérer TCP, notamment en réutilisant les connexions (il serait très coûteux d'établir une connexion TCP pour chaque requête DNS, surtout avec TLS par dessus). Mais il n'a pas de cache des réponses, ce qui peut être ennuyeux si on est loin de Quad9. Pour cela, le plus simple est d'ajouter un vrai résolveur, ici Unbound. On le configure ainsi :

server:
   interface: 127.0.0.1
   do-not-query-localhost:  no
forward-zone:
  name: "."
    forward-addr: ::1@8053
   

Avec cette configuration, Unbound va écouter sur l'adresse 127.0.0.1 (sur le port par défaut, 53, le port du DNS) et relayer les requêtes pour lesquelles il n'a pas déjà une réponse dans son cache vers Stubby (::1, port 8053). Interrogeons Unbound :


% dig @127.0.0.1 A mastodon.gougere.fr

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 A mastodon.gougere.fr
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40668
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;mastodon.gougere.fr.	IN A

;; ANSWER SECTION:
mastodon.gougere.fr.	600 IN A 185.167.17.10

;; Query time: 2662 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Nov 16 20:36:09 +08 2017
;; MSG SIZE  rcvd: 64

       

Unbound a une mémoire (le cache) donc si on recommance la requête aussitôt, la réponse arrivera bien plus vite et on verra le TTL (600 secondes ici) diminué.

Si vous trouvez que tout cela est bien compliqué à installer/configurer, vous pouvez vous servir d'une image Docker, faite par Juzam (voir aussi sur Github).

On note qu'il n'est pas évident de trouver une adresse IPv4 facilement mémorisable comme 9.9.9.9. L'examen de DNSDB montre que cette adresse a beaucoup servi avant d'arriver chez PCH, et pour des activités… que certains peuvent trouver contestables. Cette adresse, sérieusement marquée, est donc noirlistée à plusieurs endroits. Si cela ne marche pas de chez vous, essayez d'ailleurs, ou alors en IPv6. On voit bien cette imparfaite connectivité en testant avec les sondes RIPE Atlas et le programme atlas-reach, comparant Quad9 et Google Public DNS :

% atlas-resolve -r 200 -e 9.9.9.9 --nsid -t AAAA irtf.org
Nameserver 9.9.9.9
[ERROR: SERVFAIL] : 1 occurrences
[TIMEOUT] : 9 occurrences
[2001:1900:3001:11::2c] : 177 occurrences
Test #10205081 done at 2017-11-16T01:41:40Z

% atlas-resolve -r 200 -e 8.8.8.8 -g 10205081 --nsid -t AAAA irtf.org
Nameserver 8.8.8.8
[TIMEOUT] : 1 occurrences
[2001:1900:3001:11::2c] : 186 occurrences
Test #10205089 done at 2017-11-16T01:46:38Z
   

Et merci à Sara Dickinson pour son aide technique.

Si vous vous intéressez aux questions plus politiques, vous pouvez consulter l'article critique de sha_5_1_2 ainsi que la la réponse très détaillée du directeur de Quad9.


L'article seul

RFC 8261: Datagram Transport Layer Security (DTLS) Encapsulation of SCTP Packets

Date de publication du RFC : Novembre 2017
Auteur(s) du RFC : M. Tuexen (Muenster Univ. of Appl. Sciences), R. Stewart (Netflix), R. Jesup (WorldGate Communications), S. Loreto (Ericsson)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tsvwg
Première rédaction de cet article le 16 novembre 2017


Le protocole de transport SCTP est normalement prévu pour tourner directement sur IP. Pour diverses raisons, il peut être utile de le faire tourner sur un autre protocole de transport comme UDP (ce que fait le RFC 6951) ou même sur un protocole qui offre des services de sécurité comme DTLS (ce que fait notre nouveau RFC).

SCTP est défini dans le RFC 4960. C'est un concurrent de TCP, offrant un certain nombre de services que TCP ne fait pas. DTLS, normalisé dans le RFC 6347, est un protocole permettant d'utiliser les services de sécurité de TLS au-dessus d'UDP. Il est notamment très utilisé par WebRTC, lui donnant ainsi une sécurité de bout en bout.

En théorie, SCTP peut fonctionner directement sur IP. Hélas, dans l'Internet actuel, très ossifié, plein d'obstacles s'y opposent. Par exemple, les routeurs NAT tel que la box de M. Michu à la maison, n'acceptent en général que TCP et UDP. Et bien des pare-feux bloquent stupidement les protocoles que leurs auteurs sont trop ignorants pour connaître. En pratique, donc, SCTP ne passe pas partout. L'encapsuler dans un autre protocole de transport, comme UDP (directement, ou bien via DTLS), est souvent la seule solution pour avoir une connectivité. L'intérêt de DTLS est qu'on a toute la sécurité de TLS, notamment la confidentialité via le chiffrement.

Cette encapsulation est simple (section 3) : on met le paquet SCTP, avec ses en-têtes et sa charge utile, dans les données du paquet DTLS.

Il y a toutefois quelques détails à prendre en compte (section 4 de notre RFC). Par exemple, comme toute encapsulation prend quelques octets, la MTU diminue. Il faut donc un système de PMTUD. Comme l'encapsulation rend difficile l'accès à ICMP (voir la section 6) et que les middleboxes pénibles dont je parlais plus haut bloquent souvent, à tort, ICMP, cette découverte de la MTU du chemin doit pouvoir se faire sans ICMP (la méthode du RFC 4821 est recommandée).

Cette histoire de MTU concerne tout protocole encapsulé. Mais il y a aussi d'autres problèmes, ceux liés aux spécificités de SCTP (section 6) :

  • Il faut évidemment établir une session DTLS avant de tenter l'association SCTP (« association » est en gros l'équivalent de la connexion TCP),
  • On peut mettre plusieurs associations SCTP sur la même session DTLS, elles sont alors identifiées par les numéros de port utilisés,
  • Comme DTLS ne permet pas de jouer avec les adresses IP (en ajouter, en enlever, etc), on perd certaines des possibilités de SCTP, notamment le multi-homing, pourtant un des gros avantages théoriques de SCTP par rapport à TCP,
  • Pour la même raison, SCTP sur DTLS ne doit pas essayer d'indiquer aux couches inférieures des adresses IP à utiliser,
  • SCTP sur DTLS ne peut pas compter sur ICMP, qui sera traité plus bas, et doit donc se débrouiller sans lui.

Cette norme est surtout issue des besoins de WebRTC, dont les implémenteurs réclamaient un moyen facile de fournir sécurité et passage à travers le NAT. Elle est mise en œuvre depuis longtemps, dans des clients WebRTC comme Chrome, Firefox ou Opera.


Téléchargez le RFC 8261


L'article seul

Articles des différentes années : 2018  2017  2016  2015  2014  2013  2012  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.