Je suis Charlie

Autres trucs


Seulement les RFC

Seulement les fiches de lecture


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.

Developing a monitoring plugin for DNS-over-TLS at the IETF hackathon

First publication of this article on 27 March 2017

The week-end of 25-26 march 2017, I participated to the IETF 98 hackathon in Chicago. The project was to develop a monitoring plugin for the DNS-over-TLS privacy protocol, standardized in RFC 7858. This is a small documentation of the result and of the lessons learned.

A bit of background, first. "Monitoring Plugins" is project to develop and maintain an excellent suite of testing programs to be used by many monitoring software like Icinga. Using their API was an obvious choice, allowing the plugin to be used in many places. And DNS-over-TLS? It's a way to improve privacy of DNS users by encrypting the DNS traffic (see RFC 7626 for the privacy issues of the DNS). DNS-over-TLS is described in RFC 7858, published less than one year ago. DNS-over-TLS is implemented in many DNS servers (such as Unbound) and there are several public DNS-over-TLS resolvers. All of them are experimental, "best effort" services and thus some monitoring is a good idea, so we can be sure they actually work most of the time. Existing monitoring plugins like check_dig cannot run with TLS.

The IETF hackathon is intended for development of IETF-related techniques. A monitoring plugin for this DNS-over-TLS service is a good fit for a hackathon: hard enough to require some work, but small enough to be reasonably completed in one week-end.

I prepared for the hackathon by setting up a Github repository and exploring the various possibilities. I saw two alternatives:

  • Use Go because it has both a nice DNS library and a good TLS standard package. On the other hand, I'm not sure that the Monitoring Plugins project accept plugins written in Go (I did not find precise rules about that). And the command-line arguments parsing package of Go may make difficult to follow exactly the rules of the API.
  • Use C with the getdns package, which can do DNS over TLS (and many other things). Because most monitoring plugins are written in C, there was a lot of code to start with.

I choosed C and getdns for two reasons, the availability of getdns developers at the hackathon (that's the good thing with hackathons, working with people who are at the same table), and the problem of retrieving the PKIX certificate. Why did I need this certificate? Because I wanted to test things that are TLS-specific, such as a nearby expiration, by far the most common problem with TLS servers.

Using Go and the godns library, it is easy to do a DNS-over-TLS request with the Exchange() function. It is easy because it hides everything from the programmer. But it is also what makes it unsuitable for my purpose, it hides the TLS details and provides no way to retrieve the certificate. A possible solution would be to use godns only to create and parse DNS messages and to call directly the Go network and TLS libraries to send messages and receive responses. Then, I would have the certificate in the conn object. Certainly doable, but more work. So, I used C and getdns.

At first glance, it was not better, getdns does not give access to the certificate of the TLS connection. But this is what makes hackathons great: the developer of the library you use is in the same room and you can ask him "Willem, could you add this cool feature?", and a few minutes after, the feature is available in a git development branch. Basically, the new stuff uses the return_call_reporting getdns extension:

getdns_dict_set_int(extensions, "return_call_reporting",

and then you have a dictionary member call_reporting in the answer:

getdns_list    *report_list;
getdns_dict    *report_dict;
getdns_dict_get_list(this_response, "call_reporting", &report_list);
getdns_list_get_dict(report_list, 0, &report_dict);

The dictionary in the report has now a new member, tls_peer_cert (it will appear in getdns 1.1):

getdns_bindata *cert;
getdns_dict_get_bindata(report_dict, "tls_peer_cert", &cert);

To parse this certficate (which is in DER format), I use GnuTLS:

gnutls_datum_t  raw_cert;
time_t          expiration_time;
struct tm      *f_time;
raw_cert.size = cert->size; = malloc(cert->size);
memcpy(, cert->data, cert->size);
gnutls_x509_crt_import(parsed_cert, &raw_cert, GNUTLS_X509_FMT_DER);
expiration_time = gnutls_x509_crt_get_expiration_time(parsed_cert);
strftime(msgbuf, 1000, "%Y-%m-%d", f_time);
printf("Expires on %s\n", msgbuf);

Now, I can test things like an incoming expiration of the certificate.

Another touchy issue was authentication. RFC 7858 allows to authenticate the server by a pinned cryptographic key. (Another authentication methods are under development at the IETF, see draft-ietf-dprive-dtls-and-tls-profiles).) That's another problem for Go, by the way: authentication is inflexible, and done by the TLS library. For getdns, on the contrary, is easy: just provide the pinned keys and getdns does the necessary checks:

keys = getdns_pubkey_pin_create_from_string(this_context, raw_keys);
getdns_list    *keys_list = getdns_list_create();
getdns_list_set_dict(keys_list, 0, keys);
getdns_dict_set_list(this_resolver, "tls_pubkey_pinset", keys_list);

and the result of the authentication is reported in the "call reporting" dictionary we already saw:

getdns_bindata *auth_status;
getdns_dict_get_bindata(report_dict, "tls_auth_status", &auth_status);
printf("Authentication is %s\n", auth_status->data);

Now, let's put it all together, compile and test from the command-lines (the arguments are the standard ones for the monitoring plugins, the servers are public servers):

% ./check-dns-with-getdns -H 2620:ff:c000:0:1::64:25 -n
GETDNS OK - 121 ms, expiration date 2027-08-25, auth. Failed:  Address 2400:cb00:2048:1::6814:55 Address 2400:cb00:2048:1::6814:155 Address Address

% echo $?

(We ask the return code of the command but this is what the monitoring software uses to find out whether everything is fine or not.) The authentication status was "Failed" because the server uses a self-signed certificate (otherwise, we would have obtained "None"). Here, we did not require authentication, so the global result is still OK. Should we provide the pinned key, it would be better:

% ./check-dns-with-getdns -H 2620:ff:c000:0:1::64:25 -n -k pin-sha256=\"pOXrpUt9kgPgbWxBFFcBTbRH2heo2wHwXp1fd4AEVXI=\"
GETDNS OK - 1667 ms, expiration date 2027-08-25, auth. Success:  Address 2001:67c:2218:30::24 Address

% echo $?

If the key is wrong, it fails:

% ./check-dns-with-getdns -H 2620:ff:c000:0:1::64:25 -n -a -k pin-sha256=\"pOXrpUt9kgPgbWxBFFcBTbRI2heo2wHwXp1fd4AEVXI=\"       
GETDNS CRITICAL - 123 ms, expiration date 2027-08-25, auth. Failed:  Address 2001:67c:2218:30::24 Address

And if the key is wrong and we require authentication (-r), we get a fatal error:

 % ./check-dns-with-getdns -H 2620:ff:c000:0:1::64:25 -n -r -k pin-sha256=\"pOXrpUt9kgPgbWxBFFcBTbRI2heo2wHwXp1fd4AEVXI=\" 
GETDNS CRITICAL - Error Generic error (1) when resolving at 2620:ff:c000:0:1::64:25

% echo $?

And of course, if the server has no DNS-over-TLS or if the server is down, or access to port 853 blocked, we also get an error:

% ./check-dns-with-getdns -H -n                
GETDNS CRITICAL - Error Generic error (1) when resolving at

% echo $?                                            

(You can also appreciate the lack of details in error messages…)

Once it is tested, we can put it in a monitoring program. I choosed Icinga. The configuration is:

object CheckCommand "dns_with_getdns" {
  command = [ PluginContribDir + "/check_dns_with_getdns" ]

  arguments = {
        "-H" = "$address$",
        "-n" = "$dns_with_getdns_lookup$",
        "-a" = "$dns_with_getdns_authenticate$",
        "-e" = "$dns_with_getdns_accept_errors$",
        "-r" = "$dns_with_getdns_require_auth$",
	"-k" = "$dns_with_getdns_keys$",
        "-C" = "$dns_with_getdns_certificate$"

apply Service "dns-tls" {
  import "generic-service"

  check_command = "dns_with_getdns"
  assign where (host.address || host.address6) && host.vars.dns_over_tls
  vars.dns_with_getdns_lookup = ""
  vars.dns_with_getdns_certificate = "7,3"
  vars.dns_with_getdns_accept_errors = false
object Host "oarc-dns" {
  import "generic-host"

  address = "2620:ff:c000:0:1::64:25"

  vars.dns_over_tls = true

  vars.dns_with_getdns_authenticate = true
  vars.dns_with_getdns_keys = "pin-sha256=\"pOXrpUt9kgPgbWxBFFcBTbRH2heo2wHwXp1fd4AEVXI=\""

Then we get the goal of every hackathon project: a screenshot .

Now, I'm not sure if I'll have time to continue to work on this project. There are several TODO in the code, and an ambitious goal: turn it into a proper plugion suitable for inclusion on the official Monitoring Plugins project. Even better would be to have a generic DNS checker based on getdns, replacing the existing plugins which depend on external commands such as dig. If you want to work on it, the code is at Github.

Many thanks to Willem Toorop for a lot of help and getdns additions, to Francis Dupont for debugging a stupid C problem with GnuTLS (garbage data, unaligned access, all the pleasures of C programming), and to Sara Dickinson for help, inspiration and animation of the DNS team.

L'article seul

Le transit Internet est-il vraiment mort ?

Première rédaction de cet article le 21 mars 2017

À la réunion APRICOT / APNIC du 20 février au 2 mars, à Hô-Chi-Minh-Ville, Geoff Huston a fait un exposé remarqué, au titre provocateur, « The death of transit ». A-t-il raison de prédire la fin du transit Internet ? Et pourquoi est-ce une question importante ?

Deux petits mots de terminologie, d'abord, s'inscrivant dans l'histoire. L'Internet avait été conçu comme un réseau connectant des acteurs relativement égaux (par exemple, des universités), via une épine dorsale partagée (comme NSFnet). Avec le temps, plusieurs de ces épines dorsales sont apparues, l'accès depuis la maison, l'association ou la petite entreprise est devenu plus fréquent, et un modèle de séparation entre les FAI et les transitaires est apparu. Dans ce modèle, le client se connecte à un FAI. Mais comment est-ce que les FAI se connectent entre eux, pour que Alice puisse échanger avec Bob, bien qu'ils soient clients de FAI différents ? Il y a deux solutions, le peering et le transit. Le premier est l'échange de trafic (en général gratuitement et informellement) entre des pairs (donc plus ou moins de taille comparable), le second est l'achat de connectivité IP, depuis un FAI vers un transitaire. Ces transitaires forment donc (ou formaient) l'épine dorsale de l'Internet. Le modèle de l'Internet a été un immense succès, au grand dam des opérateurs téléphoniques traditionnels et des experts officiels qui avaient toujours proclamé que cela ne marcherait jamais.

Mais une autre évolution s'est produite. Les utilisateurs ne se connectent pas à l'Internet pour le plaisir de faire des ping et des traceroute, ils veulent communiquer, donc échanger (des textes, des images, des vidéos…) À l'origine, l'idée était que l'échange se ferait directement entre les utilisateurs, ou sinon entre des serveurs proches des utilisateurs (ceux de leur réseau local). Le trafic serait donc à peu près symétrique, dans un échange pair-à-pair. Mais les choses se ne passent pas toujours comme ça. Aujourd'hui, il est de plus en plus fréquent que les communications entre utilisateurs soient médiées (oui, ce verbe est dans le Wiktionnaire) par des grands opérateurs qui ne sont pas des opérateurs de télécommmunication, pas des transitaires, mais des « plate-formes » comme les GAFA (Google, Apple, Facebook, Amazon). La communication entre utilisateurs n'est plus pair-à-pair mais passe par un intermédiaire. (On peut parler d'un Minitel 2.0.)

Bon, mais quel rapport avec l'avenir de l'Internet ? Mes lect·eur·rice·s sont très cultivé·e·s et savent bien que le Web, ce n'est pas l'Internet, et que le fait que deux utilisateurs de Gmail passent par Gmail pour communiquer alors qu'ils sont à 100 mètres l'un de l'autre n'est pas une propriété de l'Internet. (Les ministres et la plupart des journalistes n'ont pas encore compris cela, mais ça viendra.) L'Internet continue à fonctionner comme avant et on peut toujours faire du BitTorrent, et se connecter en SSH avec un Raspberry Pi situé à l'autre bout de la planète. (Notez qu'il s'agit de l'Internet en général : dans la quasi-totalité des aéroports et des hôtels, de nombreux protocoles sont interdits. Et ces malhonnêtes osent prétendre qu'ils fournissent un « accès Internet ».)

C'est là qu'on en arrive à l'exposé de Huston. Il note d'abord que les sites Web qui ne sont pas déjà chez un GAFA sont souvent hébergés sur un CDN. Ensuite, il fait remarquer que les GAFA, comme les CDN, bâtissent de plus en plus leur propre interconnexion. À ses débuts, Google était une entreprise comme une autre, qui achetait sa connectivité Internet à un fournisseur. Aujourd'hui, Google pose ses propres fibres optiques (ou achète des lambdas) et peere avec les FAI : encore un peu et Google n'aura plus besoin de transit du tout. Si tous les GAFA et tous les CDN en font autant (et la plupart sont déjà bien engagés dans cette voie), que deviendra le transit ? Qui pourra encore gagner sa vie en en vendant ? Et si le transit disparait, l'architecture de l'Internet aura bien été modifiée, par l'action de la minitélisation du Web. (Je résume beaucoup, je vous invite à lire l'exposé de Huston vous-même, ainsi que son article sur des thèmes proches.)

Notez que Huston n'est pas le premier à pointer du doigt cette évolution. Plusieurs articles moins flamboyants l'avaient déjà fait, comme les déjà anciens « The flattening internet topology: natural evolution, unsightly barnacles or contrived collapse? » ou « Internet Inter-Domain Traffic ». Mais Huston réussit toujours mieux à capter l'attention et à résumer de manière percutante un problème complexe.

Alors, si Huston a raison, quelles seront les conséquences de la disparition du transit ? Huston note qu'une telle disparition pourrait rendre inutile le système d'adressage mondial (déjà très mal en point avec l'épuisement des adresses IPv4 et la prévalence du NAT), voire le système de nommage mondial que fournit le DNS. Le pair-à-pair, déjà diabolisé sur ordre de l'industrie du divertissement, pourrait devenir très difficile, voire impossible. Aujourd'hui, même si 95 % des utilisateurs ne se servaient que des GAFA, rien n'empêche les autres de faire ce qu'ils veulent en pair-à-pair. Demain, est-ce que ce sera toujours le cas ?

Mais est-ce que Huston a raison de prédire la mort du transit ? D'abord, je précise que je suis de ceux qui ne croient pas à la fatalité : ce sont les humains qui façonnent l'histoire et les choses peuvent changer. Décrire la réalité, c'est bien, mais il faut toujours se rappeler que c'est nous qui la faisons, cette réalité, et que nous pouvons changer. Essayons de voir si les choses ont déjà changé. Huston aime bien provoquer, pour réveiller son auditoire. Mais il faut bien distinguer l'apparence et la réalité.

Les observateurs légers croient que tout l'Internet est à leur image. Comme eux-même ne se servent que de Gmail et de Facebook, ils expliquent gravement en passant à la télé que l'Internet, c'est Google et Facebook. Mais c'est loin d'être la totalité des usages. Des tas d'autres usages sont présents, par exemple dans l'échange de données entre entreprises (y compris via d'innombrables types de VPN qui transportent leurs données… sur Internet), les SCADA, BitTorrent, la recherche scientifique et ses pétaoctets de données, les réseaux spécialisés comme LoRa, les chaînes de blocs, et ces usages ne passent pas par les GAFA.

Peut-on quantifier ces usages, pour dire par exemple, qu'ils sont « minoritaires » ou bien « un détail » ? Ce n'est pas facile car il faudrait se mettre d'accord sur une métrique. Si on prend le nombre d'octets, c'est évidemment la vidéo qui domine et, à cause du poids de YouTube, on peut arriver à la conclusion que seuls les GAFA comptent. Mais d'autres critères sont possibles, quoique plus difficiles à évaluer (le poids financier, par exemple : un message d'une entreprise à une autre pour un contrat de centaines de milliers d'euros pèse moins d'octets qu'une vidéo de chat, mais représente bien plus d'argent ; ou bien le critère de l'utilité sociale). Bref, les opérateurs de transit sont loin d'être inutiles. L'Internet n'est pas encore réduit à un Minitel (ou à une télévision, l'exemple que prend Huston qui, en bon australien, ne connait pas ce fleuron de la technologie française.)

Merci à Antoine Fressancourt, Jérôme Nicolle, Pierre Beyssac, Raphaël Maunier, Olivier Perret, Clément Cavadore et Radu-Adrian Feurdean pour leurs remarques intéressantes. Aucune de ces conversations avec eux n'est passée par un GAFA.

L'article seul

Suite de mes aventures avec le routeur Turris Omnia

Première rédaction de cet article le 19 mars 2017

Dans un précédent article, j'ai parlé du routeur Turris Omnia et de ce qu'on peut faire avec. Ce deuxième article est un assortiment de diverses choses que j'ai faites depuis avec ce routeur.

Il est connecté à Free en ADSL. Plus exactement, l'ADSL arrive sur une Freebox Révolution, configurée en bridge, à laquelle est relié le Turris Omnia, qui est le vrai routeur. À l'origine, j'avais laissé le Freebox Player connecté au Freebox Server, ce qui faisait que la télévision classique et le téléphone marchaient comme avant. Mais comme je voulais regarder les chaînes de télévision depuis un PC, avec VLC et le protocole RTSP (RFC 7826), il fallait connecter le Freebox Player au routeur. Je me suis beaucoup inspiré de cet excellent article. Donc, ce qu'il fallait faire :

  • Connecter le câble Ethernet entre le Turris et le Freebox Player (celui-ci n'a donc plus de câble vers le Freebox Server),
  • Configurer le commutateur du Turris pour utiliser le VLAN 100, celui sur lequel Freebox Player et Server communiquent.

Attention en jouant avec la configuration du commutateur interne du Turris : une erreur et on se retrouve vite avec des tempêtes de diffusion, qui peuvent aller jusqu'à rendre le routeur inaccessible en Ethernet. J'ai aussi eu un cas amusant où la plupart des paquets étaient bien transmis, sauf ceux de diffusion, ce qui cassait des protocoles comme ARP ou DHCP. Deux conseils : vérifier que le Wi-Fi fonctionne, il peut servir de mécanisme de secours pour se connecter au Turris, si l'Ethernet devient inutilisable. Et bien relire sa configuration avant de la confirmer. Dans le pire des cas, il faudra perdre toute la configuration en remettant le routeur aux réglages d'usine (pensez à garder cette documentation avant de vous couper votre accès Internet !)

Vu du côté Unix, le Turris a plein d'interfaces réseau. eth0 rassemble la plupart des ports physiques du commutateur, eth2 étant le CPU (et le port 4 du commutateur, voir cette discussion sur le forum). Voici d'ailleurs un schéma :

Ensuite, des interfaces virtuelles regroupent plusieurs de ces interfaces (sour l'interface LuCI, vous trouver cette configuration en Network -> Interfaces, https://ROUTER/cgi-bin/luci/admin/network/network). Par exemple, br-lan regroupe typiquement eth0 et eth2. Et c'est ainsi que les deux groupes communiquent (sur LuCI, Network -> Interfaces puis Edit puis Physical settings).

Et les VLAN ? Ils se configurent/s'affichent avec LuCI en Network -> Switch, https://ROUTER/cgi-bin/luci/admin/network/vlan. Par défaut, tous les ports sont non marqués (untagged) ce qui veut dire que le commutateur ne fait pas de VLAN. Si on branche le Freebox Player sur un port où on active le marquage (tagged pour le VLAN 100, celui utilisé par les boitiers Freebox) et le Freebox Server sur un autre port marqué 100, les deux boitiers peuvent communiquer, la télévision marche mais, dans ce cas, le réseau local, toujours non marqué, ne peut plus communiquer avec ces boitiers et on n'a donc pas d'accès Internet. La configuration qui marche est donc celle-ci : Le port marqué CPU dans LuCI est celui qui est marqué WAN sur le boitier (je sais, c'est bizarre).

Vous n'aimez pas les copies d'écran, vous préférez des fichiers de configuration ? Pas de problème, cela se configure dans /etc/config/network (je n'ai montré que les paramètres pertinents) :

config interface 'lan'
	option type 'bridge'
	option ifname 'eth0 eth2'
config interface 'Freebox'
	option type 'bridge'
	option proto 'static'
	option ifname 'eth0.100 eth1.100'

config switch_vlan
	option device 'switch0'
	option vlan '1'
	option vid '1'
	option ports '1 2 3 4 5'

config switch_vlan
	option device 'switch0'
	option vlan '2'
	option ports '0t 5t'
	option vid '100'

Pour résumer cette configuration : on a deux VLANs, 1 et 100. 100 (deuxième directive config switch_vlan) couvre le port 0 (qui est marqué, et où est connecté le Freebox Player) et le port CPU/WAN/5 - connecté au Freebox Server - qui est le seul à être sur deux VLAN (1 en non marqué et 100 en marqué). Le t dans la liste des ports indique un marquage (tagging). L'autre VLAN, 1 (première directive config switch_vlan), couvre les autres ports. Pour que les interfaces physiques communiquent, on a deux ponts, br-lan (directive config interface 'lan') et br-Freebox, qui fait communiquer les deux ports du VLAN 100 (qui arrivent sur des commutateurs différents, regardez le schéma plus haut). Les ports marqués correspondent aux interfaces comportant le numéro du VLAN (comme eth0.100, les paquets du VLAN 100 arrivant sur eth0)

(Au passage, si vous utilisez LuCI pour configurer, vous devrez cliquer sur Save and apply pour appliquer votre configuration. Rappelez-vous de bien la vérifier avant. Si vous avez au contraire édité le fichier de configuration à la main, ce sera un /etc/init.d/network restart, avec les mêmes précautions.)

Avec tout ça, tout le monde communique, la télé marche (si le Freebox Player affiche au démarrage qu'il ne peut pas communiquer avec le Freebox Server, c'est que vous avez un problème), l'Internet fonctionne, etc. Mais on ne peut toujours pas regarder la télévision avec VLC (vlc affiche live555 demux error: no data received in 10s, aborting). La raison en est que RTSP est un protocole un peu spécial (il n'est pas vraiment client/serveur) : certes, le PC se connecte à la Freebox mais le flux vidéo lui-même n'est pas envoyé dans cette connexion, mais séparement sous forme de paquets UDP. Le Turris n'a apparemment pas de mécanisme de suivi des sessions RTSP (conntracker, comme ce module) qui permettrait de transmettre automatiquement ces paquets UDP à la bonne machine. J'ai donc choisi, en suivant cette excellente documentation, de configurer le Turris pour chaque machine. Sur chaque PC du réseau local qui veut regarder des conneries à la télé, il faut fixer le port dans VLC Paramètres -> Préférences -> Input/Codecs->Demuxers -> RTP/RTSP. Là on coche la case Options avancées. On voit s'afficher un champ Client port, avec la valeur -1, ce qui signfie que VLC choisit aléatoirement le port d'entrée. On met la valeur de son choix (attention, elle doit être paire), par exemple 31336. Il faut aussi configurer le Turris pour transmettre ce port à la bonne machine. (Oui, tout serait plus pratique si avait une adresse IPv6). Dans LuCI, c'est dans Network -> Firewall puis Port forwards ROUTER/cgi-bin/luci/admin/network/firewall/forwards : Et si vous préférez cette configuration en mode texte, c'est dans /etc/config/firewall :

config redirect
	option target 'DNAT'
	option name 'RTSP machine1'
	option proto 'udp'
	option src 'wan'
	option src_dport '31336'
	option dest 'lan'
	option dest_ip '192.168.X.Y'
	option dest_port '31336'
	option src_ip ''

Une fois qu'on a ses VLAN comme on veut, on peut s'avachir devant la télé qu'on reçoit sur son PC, ou bien on peut passer à une autre tâche. Installer un disque dur supplémentaire dans l'Omnia et créer des machines virtuelles (les deux tâches sont liées, pour des raisons expliquées plus loin).

Pourquoi un disque supplémentaire, pourquoi ne pas se contenter de la Flash présente ? Cet espace de stockage est largement suffisant (8 Go) pour faire tourner les fonctions de base du routeur, mais il ne l'est plus si on veut installer des applications, par exemple de supervision ou de statistiques, qui vont stocker des données sur le long terme, ou bien si on veut mettre son serveur de messagerie sur le Turris. Ou encore si on veut s'en servir comme NAS. Si on veut réaliser la promesse de la page Web officielle, « More than just a router. The open-source [sic] center of your home », il faudra plus de huit gigas.

D'autant plus que la Flash a un autre problème, elle s'use vite quand on écrit souvent. Voilà pourquoi, dans OpenWrt, par défaut, /var est en mémoire, et donc un équivalent de /tmp, qui ne survit pas aux redémarrages. Autrement, des services comme syslog démoliraient la Flash trop vite.

Donc, installons un disque supplémentaire. L'Omnia a un emplacement libre, au dessus de l'emplacement pour carte SIM, où on peut mettre un disque SSD via une interface mSATA. J'ai acheté un Kingston mS200 de 120 Go à 50 € TTC. Mais c'est ensuite que les ennuis commencent. L'emplacement libre dans l'Omnia n'est pas celui qui a le port combiné miniPCIexpress/mSATA, le bon emplacement est occupé par une des deux cartes Wi-Fi, il va donc falloir ouvrir le routeur, et déplacer la carte Wi-Fi. (On pourrait évidemment utiliser un disque externe, connecté en USB mais une de mes motivations pour tout mettre sur le Turris Omnia était de diminuer le nombre de boitiers et de prises de courant.)

La procédure nécessite donc tournevis et une certaine habileté manuelle. Elle est très bien expliquée dans ce film (les commentaires de la vidéo valent également d'être lus). Notez toutefois que dans mon cas, cela n'a pas suffi : les vis du dessus des cartes Wi-Fi ne se défont pas et j'ai donc dû démonter la carte Wi-Fi en l'attaquant de l'autre côté de la carte mère. (Vous trouverez aussi sur le forum Turris des discussions sur cette procédure, comme ici.) Voici le Turris Omnia ouvert avant l'opération : Et le même après, la carte Wi-Fi qui était tout à droite ayant été déplacée tout à gauche : Vous pouvez aussi télécharger une image en haute définition. Attention notamment aux fils qui vont des cartes Wi-Fi aux antennes, ils se défont facilement.

Une fois le disque branché et bien branché, la carte mère replacée et le capot fermé, on redémarre le routeur (en priant, si on est croyant). On doit voir un disque en /dev/sda (tapez dmesg | grep sda après le démarrage). On le formate comme indiqué, par exemple, dans la documentation d'OpenWrt. Chez moi, cela donne :

# fdisk -l /dev/sda

Disk /dev/sda: 111.8 GiB, 120034123776 bytes, 234441648 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 0263F4A2-3C22-4043-B2ED-32B962569924

Device         Start       End   Sectors  Size Type
/dev/sda1       2048 121636863 121634816   58G Linux filesystem
/dev/sda2  121636864 234441614 112804751 53.8G Linux filesystem

# blkid 
/dev/mmcblk0p1: UUID="0eec9a72-3c0f-4222-ab9e-2147243a7c1e" UUID_SUB="6b8deab1-dff4-48fc-a522-395f67d59de8" TYPE="btrfs" PARTUUID="2cbb06e2-01"
/dev/sda1: UUID="cb35ae3d-78f8-49f9-bbbb-efbab97b4a81" TYPE="ext4" PARTUUID="ab197dd0-71d2-446c-80e6-bf8810b86ebd"
/dev/sda2: UUID="df1c2ed7-5728-4629-9d72-263bbf2b5939" TYPE="ext4" PARTUUID="3673e386-6636-40e8-bf08-b32899b6e7c3"
/dev/mmcblk0: PTUUID="2cbb06e2" PTTYPE="dos"

On peut ensuite monter le disque de la manière OpenWrt habituelle. Voici mon /etc/config/fstab :

config mount
	option enabled '1'
	option target '/srv'
	option uuid 'cb35ae3d-78f8-49f9-bbbb-efbab97b4a81'

config mount
	option enabled '1'
	option uuid 'df1c2ed7-5728-4629-9d72-263bbf2b5939'
	option target '/var'

Pour compléter, notez que ce déplacement d'une carte Wi-Fi va nécessiter de reconfigurer le service Wi-Fi (dans LuCI, Network -> Wireless), la carte passant de radio1 à radio2.

Une fois qu'on a son disque, on peut installer ses machines virtuelles ou plus exactements ses containers. Pourquoi ces machines supplémentaires alors qu'on a déjà un Unix qui tourne parfaitement sur le matériel ? Mon problème était surtout que le nombre de paquetages est très limité sur l'Omnia (cf. la liste). Il n'y a ainsi pas emacs. Les outils de développement sont absents (on peut éventuellement faire de la compilation croisée) et, de toute façon, il y a deux bonnes raisons pour ne pas installer plein de choses sur l'Unix OpenWrt de l'Omnia :

  • Seuls les paquetages officiels bénéficients d'une fonction essentielle de l'Omnia (notamment pour la sécurité), la mise à jour automatique.
  • Le routeur doit router dans tous les cas, et doit donc avoir un jeu de logiciels minimum. Tout ce qu'on rajoute peut créer des problèmes.

Donc, la méthode propre sur Omnia, si on veut des logiciels comme Icinga (pour la supervision) ou des petits utilitaires sympa comme uptimed ou comme check-soa (indispensable quand on joue souvent avec le DNS), la méthode propre, donc, est d'installer des machines virtuelles sur l'Omnia.

En fait, ce ne sont pas des machines virtuelles complètes, juste des containers, avec la technique LXC. Contrairement à des vraies machines virtuelles, ils ne fournissent pas une étanchéité complète. Tous utilisent le même noyau, qui ne s'exécute qu'une fois. (C'est d'ailleurs pour cela qu'uptimed dans un container marche bien : il enregistre l'uptime du routeur, pas celui du container.) Les containers n'ont pas non plus d'horloge propre et c'est pour cela qu'ils n'ont pas besoin de NTP, celui du routeur suffit.

Autre conséquence du modèle du container, les « machines » doivent tourner avec Linux, pas de FreeBSD sur le Turris Omnia. LXC sur cette machine est bien documenté. Voici le processus de création d'un container, avec le choix des systèmes d'exploitation :

#  lxc-create -t download -n gandalf
Setting up the GPG keyring
Downloading the image index

Turris_OS	stable	armv7l	default	2016-11-27
Turris_OS	stable	ppc	default	2016-11-27
Alpine	3.4	armv7l	default	2016-11-27
Debian	Jessie	armv7l	default	2016-11-27
Gentoo	stable	armv7l	default	2016-11-27
openSUSE	13.2	armv7l	default	2016-11-27
openSUSE	Tumbleweed	armv7l	default	2016-11-27
Ubuntu	Xenial	armv7l	default	2016-11-27
Ubuntu	Yakkety	armv7l	default	2016-11-27

Distribution: Debian
Release: Jessie
Architecture: armv7l

Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs

Distribution Debian version Jessie was just installed into your container.

Content of the tarballs is provided by third party, thus there is no warranty of any kind.

Pas d'Arch Linux, je le regrette, donc j'ai mis Debian.

Ensuite, on démarre le container :

# lxc-start -n gandalf

On s'y attache :

# lxc-attach -n gandlf

Et on peut configurer le mot de passe, le réseau (je n'ai pas réussi à faire marcher le client DHCP sur les containers, j'ai tout configuré en statique), SSH… (Notez qu'on peut aussi faire tout cela depuis LuCI, Services -> LXC containers.) La configuration du container gandalf se retrouve dans /srv/lxc/gandalf/config. On peut notamment configurer l'adresse MAC du container (attention, si ce n'est pas fait, le container change d'adresse MAC à chaque démarrage, ce qui est excellent pour la vie privée mais moins pour l'administration système, avec arpwatch et NDPMon qui voient une nouvelle machine à chaque fois) :

# cat /srv/lxc/gandalf/config 
# Network configuration = veth = br-lan = up = eth0 = /usr/share/lxc/hooks/tx-off = 21:ae:a4:79:73:16

Une fois qu'on a un beau container qui tourne, on peut y installer ses logiciels favoris, comme Icinga (qui, avant, tournait chez moi sur un Raspberry Pi).

Le troisième grand dossier, après les VLAN et l'ajout du disque, c'était la configuration du résolveur DNS. Le Turris utilise par défaut kresd, alias Knot resolver. Intéressant logiciel, quoique ayant encore quelques défauts de jeunesse. Knot marche bien par défaut, et fournit notamment la validation DNSSEC :

% dig A
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47317
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 6, AUTHORITY: 0, ADDITIONAL: 1

Le AD (Authentic Data dans les flags) indique que le nom est signé et vérifié. Avec un nom pas signé, on n'a pas ce AD :

% dig A       
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16026
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

Et, si le nom est signé mais erroné, on récupère un SERVFAIL (Server Failure) :

% dig A    
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 28366
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

% dig +cd A 
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25881
;; flags: qr rd ra cd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

(Le +cd voulant dire Checking Disabled, pour être sûr que le problème vient de DNSSEC.)

Le premier changement que j'ai fait à sa configuration était de couper la transmission (forwarding) aux résolveurs du FAI (qui sont des résolveurs menteurs) : option forward_upstream '0' dans /etc/config/resolver. (Si, à l'inverse, on veut transmettre à des serveurs aval spécifiques, voir cette discussion sur le forum.)

Mais je voulais surtout une configuration spéciale pour utiliser la racine Yeti. Cela nécessite la configuration suivante. D'abord, /etc/config/resolver :

config resolver 'common'
        option keyfile '/etc/kresd/yeti-root.keys'

config resolver 'kresd'
        option include_config '/etc/kresd/custom.conf'

Le fichier des clés de Yeti, indispensable pour la validation DNSSEC, se récupère chez Yeti (et est réécrit ensuite par Knot, qui gère le RFC 5011). Ensuite, le /etc/kresd/custom.conf contient :

        [''] = '240c:f:1:22::6',
        [' .'] = '2001:4f8:3:1006::1:4',
        [''] = '2001:200:1d9::35',
        [''] = '2001:4b98:dc2:45:216:3eff:fe4b:8c5b',
        [''] = '2001:67c:217c:6::2',

(Pas grave s'il manque un ou deux serveurs, le primingRFC 8109 - s'en occupe.)

Voilà, le résolveur utilise désormais la racine Yeti, comme on peut le vérifier facilement :

% dig NS .
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46120
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 26, AUTHORITY: 0, ADDITIONAL: 1
.			86400 IN NS
.			86400 IN NS
.			86400 IN NS
.			86400 IN NS
.			86400 IN NS

Notez que kresd peut être interrogé via une console CLI :

# socat - UNIX-CONNECT:/tmp/kresd/tty/$(pidof kresd)

Et on a droit alors à plein d'informations amusantes (tapez help() pour la liste ou bien regardez la documentation) :

> cache.count()

> cache.stats()
[hit] => 12259583
[delete] => 1
[miss] => 24413638
[insert] => 1542550

> stats.list()     
[answer.nxdomain] => 775648
[answer.100ms] => 102752
[answer.1500ms] => 27366
[answer.slow] => 72019
[answer.servfail] => 354445
[answer.250ms] => 125256
[answer.cached] => 3062179
[answer.nodata] => 206878
[query.dnssec] => 80582
[answer.1ms] => 3054309
[predict.epoch] => 27
[query.edns] => 84111
[predict.queue] => 5946
[] => 4112245
[answer.10ms] => 77419
[answer.noerror] => 2775274
[answer.50ms] => 393935
[answer.500ms] => 205149
[answer.1000ms] => 47949
[predict.learned] => 447

> cache.get("")
[] => {
    [CNAME] => true
[] => {
    [CNAME] => true
[] => {
    [CNAME] => true
[] => {
    [A] => true
[] => {
    [A] => true
    [AAAA] => true
[] => {
    [A] => true
[] => {
    [CNAME] => true
[] => {
    [CNAME] => true

Après ces trois « grands dossiers », voici plein de petits détails et de petits projets plus ou moins amusants.

Un des avantages d'un routeur qu'on contrôle complètement, où on est root, c'est qu'on peut tout configurer, y compris les diodes luminescentes, si indispensables à l'informatique. On peut par exemple changer la couleur des diodes selon le débit. La technique en Lua expliquée sur le forum marche très bien.

Par défaut, le Turris Omnia se gère en HTTPS avec un certificat auto-signé. Même si ce n'est que sur le réseau local, ce n'est pas satisfaisant (et ça empêche les navigateurs de mémoriser les mots de passe, le site n'étant pas considéré comme sûr). Comme je suis utilisateur de CAcert, je voulais utiliser HTTPS avec un certificat CAcert. On le crée (openssl req -new -nodes -newkey rsa:2048 -keyout server.key -out server.csr -days 1000), on le fait signer via l'interface Web du CAcert (tout est gratuit et automatique dans CAcert) et on concatène clé privée et certificat (c'est le format qu'attend le serveur HTTPS du Turris, lighthttpd, cf. cet article du forum) :

# cat server.key  server.pem  > /etc/lighttpd/tls/server.pem

Et on change la configuration HTTPS (/etc/lighttpd/conf.d/ssl-enable.conf) :

$SERVER["socket"] == ":443" {
	ssl.engine = "enable"
        ssl.pemfile = "/etc/lighttpd/tls/server.pem"
	ssl.use-sslv2 = "disable"
	ssl.use-sslv3 = "disable"

(Et idem avec $SERVER["socket"] == "[::]:443" pour IPv6.) En prime, j'active HSTS (RFC 6797) :

$HTTP["scheme"] == "https" {
	# Add  'HTTP Strict Transport Security' header (HSTS) to sites
	setenv.add-response-header  += ( "Strict-Transport-Security" => "max-age=31536000; includeSubDomains" )

Et je mets une redirection en place depuis HTTP vers HTTPS, dans /etc/lighttpd/conf.d/https-redirect.conf :

$HTTP["scheme"] == "http" {
    # capture vhost name with regex conditiona -> %0 in redirect pattern
        # must be the most inner block to the redirect rule
	    $HTTP["host"] =~ ".*" {
	            url.redirect = (".*" => "https://%0$0")

Tout marche bien, désormais.

Un problème fréquent des tunnels (comme celui qu'utilise Free pour faire passer l'IPv6 vers les clients ADSL est que, la MTU ayant été diminuée, les paquets d'une taille proche de la MTU traditionnelle de 1 500 octets ne passent plus. Cela se voit lorsque ping (avec la taille par défaut) ou openssl s_client passent mais que HTTP n'arrive pas à faire passer des données. a MTU configurée sur l'Omnia est de 1 480 octets :

config interface 'wan'
	option ifname 'eth1'
	option proto 'dhcp'
	option mtu '1480'

Désormais, tout passe, mais des machines du réseau local envoient toujours des paquets trop gros (je devrais peut-être diffuser la MTU réduite sur le réseau local). Le routeur note :

2016-11-02T07:47:14+00:00 err kernel[]: [  437.388025] mvneta f1034000.ethernet eth1: bad rx status 8fa50000 (max frame length error), size=1504

Il y a aussi des problèmes que je n'ai pas réussi à résoudre comme celui d'un accès anonyme aux graphes de trafic. Notez que je n'ai guère utilisé les forums génériques OpenWrt : les problèmes discutés sont souvent très spécifiques à un modèle de routeur. Par contre, la documentation d'OpenWrt est très utile si le Turris Omnia est le premier routeur OpenWrt que vous configurez sérieusement. Vous y trouverez notamment des explications sur le système de configuration /etc/config, qui peut être déroutant, si vous venez d'un Unix classique.

Et il y a des problèmes qui sont résolus (le Turris Omnia est en plein développement, et, avec les mises à jour automatiques, on voit des solutions aux problèmes arriver seules). C'est ainsi que le « socat fou » qui avait fait perdre tant de temps et d'électricité au début de l'Omnia a été réparé sans que j'ai rien eu à faire.

Le routeur Turris permet d'afficher de jolis graphes de trafic (dans LuCI, Statistics -> Graphs). La configuration n'est pas évidente (Statistics -> Setup) : j'ai dû créer à la main les répertoites indiqués dans la configuration, puis faire :

/etc/init.d/luci_statistics enable
/etc/init.d/collectd enable

Par défaut, toutes les données sont perdues à chaque démarrage (voir plus haut la discussion sur la mémoire Flash). On peut changer les répertoires de données pour le disque stable, mais cette modification est perdue à chaque mise à jour du logiciel, hélas. Bref, ce n'est pas encore satisfaisant.

L'article seul

RFC 8118: The application/pdf Media Type

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : M. Hardy, L. Masinter, D. Markovic (Adobe Systems), D. Johnson (PDF Association), M. Bailey (Global Graphics)
Pour information
Première rédaction de cet article le 18 mars 2017

Le format PDF, largement utilisé sur l'Internet, n'a sans doute pas besoin d'être présenté ici. De toute façon, ce nouveau RFC ne prétend pas décrire PDF, juste le type de contenu application/pdf. Ce RFC remplace l'ancien RFC 3778, notamment pour tenir compte du fait qu'officiellement, PDF n'est plus une spécification Adobe mais une norme ISO, 32000-1:2008.

Donc, si vous envoyez des documents PDF via l'Internet, que ce soit par courrier ou par le Web, vous êtes censé les étiqueter avec le type MIME application/pdf (le type de premier niveau applicaton/ indiquant que c'est un format binaire, non utilisable en dehors des applications spécialisées). Ce type a été enregistré à l'IANA (section 8 du RFC).

PDF avait été conçu pour le monde du papier (les commerciaux d'Adobe répétaient dans les années 90 que PDF permettait d'avoir « le même rendu partout » ce qui n'a pas de sens sur écran, où tous les écrans sont différents), ce qui se retrouve dans de nombreux concepts archaïques de PDF comme le découpage en pages. Un document PDF est un « rendu final », typiquement non modifiable, avec du texte utilisant différentes polices, des images… PDF permet également de représenter des liens hypertexte, une table des matières… On peut même inclure du JavaScript pour avoir des documents interactifs. PDF permet également le chiffrement et la signature, et a un mécanisme (en fait, plusieurs) pour placer des métadonnées, XMP. Bref, PDF est un format très complexe, ce qui explique les nombreuses failles de sécurité rencontrées par les programmes qui lisent du PDF.

La norme PDF est désormais déposée à l'ISO (ISO 32000-1) mais l'archaïque ISO ne distribue toujours pas librement ces documents. Si on veut apprendre PDF, il faut donc le télécharger sur le site d'Adobe.

Pour les protocoles où il y a une notion d'identificateur de fragment (comme les URI, où cet identificateur figure après le croisillon), PDF permet d'indiquer une partie d'un document. Cela fera partie de la future norme ISO, mais c'était déjà dans l'ancien RFC 3778. Cet identificateur prend la forme d'un ou plusieurs couples clé=valeur, où la clé est, par exemple, page=N (pour aller à la page n° N), comment=ID (aller à l'endroit marqué par l'annotation ID), zoom=S (agrandir d'un facteur S), search=MOT (aller à la première occurrence de MOT)… (Je n'ai pas réussi à faire fonctionner ces identificateurs de fragments avec le lecteur PDF inclus dans Chrome. Quelqu'un connait un logiciel où ça marche ?)

PDF a également des sous-ensembles. La norme est riche, bien trop riche, et il est donc utile de la restreindre. Il y a eu plusieurs de ces sous-ensembles de PDF normalisés (voir sections 2 et 4 du RFC). Ainsi, PDF/A, sous-ensemble de PDF pour l'archivage à long terme (ISO 19005-3:2012), limite les possibilités de PDF, pour augmenter la probabilité que le document soit toujours lisible dans 50 ou 100 ans. Par exemple, JavaScript y est interdit. PDF/X (ISO 15930-8:2008), lui, vise le cas où on envoie un fichier à un imprimeur. Il restreint également les possibilités de PDF, pour accroitre les chances que l'impression donne exactement le résultat attendu. Enfin, PDF/UA (ISO 14289-1:2014) vise l'accessibilité, en insistant sur une structuration sémantique (et non pas fondée sur l'apparence visuelle) du document. Tous ces sous-ensembles s'étiquettent avec le même type application/pdf. Ils ne sont pas mutuellement exclusifs : un document PDF peut être à la fois PDF/A et PDF/UA, par exemple.

Il existe d'innombrables mises en œuvre de PDF, sur toutes les plate-formes possible. Celle que j'utilise le plus sur Unix est Evince.

Un mot sur la sécurité (section 7 du RFC). On l'a dit, PDF est un format (trop) complexe, ce qui a des conséquences pour la sécurité. Comme l'impose la section 4.6 du RFC 6838, notre RFC inclut donc une analyse des risques. (Celle du RFC 3778 était trop limitée.) Notamment, PDF présente les risques suivants :

  • Les scripts inclus, écrits en JavaScript,
  • Le chargement de fichiers extérieurs, et les liens hypertexte vers l'extérieur,
  • Les fichiers inclus, qui peuvent être absolument n'importe quoi, et qui viennent avec leurs propres dangers (sans compter le risque de leur exportation vers le système de fichiers local).

Et c'est sans compter sur les risques plus génériques, comme la complexité de l'analyseur. Il y a eu de nombreuses failles de sécurité dans les lecteurs PDF (au hasard, par exemple CVE-2011-3332 ou bien CVE-2013-3553). La revue de sécurité à l'IETF avait d'ailleurs indiqué que les premières versions du futur RFC étaient encore trop légères sur ce point, et demandait un mécanisme pour mieux étiqueter les contenus « dangereux ».

Vous avez peut-être noté (lien « Version PDF de cette page » en bas) que tous les articles de ce blog ont une version PDF, produite via LaTeX (mais elle n'est pas toujours complète, notamment pour les caractères Unicode). Une autre solution pour obtenir des PDF de mes articles est d'imprimer dans un fichier, depuis le navigateur.

La section 2 du RFC rappelle l'histoire de PDF. La première version date de 1993. PDF a été un très grand succès et est largement utilisé aujourd'hui. Si on google filetype:pdf, on trouve « Environ 2 500 000 000 résultats » (valeur évidemment très approximative, le chiffre rond indiquant que Google n'a peut-être pas tout compté) . Si PDF a été créé et reste largement contrôlé par Adobe, il en existe une version ISO, la norme 32000-1, qui date de 2008 (pas de mise à jour depuis, bien qu'une révision soit attendue en 2017). ISO 32000-1:2008 est identique à la version PDF 1.7 d'Adobe.

Normalement, les anciens lecteurs PDF doivent pouvoir lire les versions plus récentes, évidemment sans tenir compte des nouveautés (section 5 du RFC).

Quels sont les changements depuis l'ancienne version, celle du RFC 3778 ? La principale est que le change controller, l'organisation qui gère la norme et peut donc demander des modifications au registre IANA est désormais l'ISO et non plus Adobe. Les autres changements sont :

  • Une mise à jour de la partie historique,
  • Une mise à jour de la partie sur les sous-ensembles de PDF, qui étaient moins nombreux autrefois,
  • Une section sécurité bien plus détaillée.

Téléchargez le RFC 8118

L'article seul

RFC 8106: IPv6 Router Advertisement Options for DNS Configuration

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : J. Jeong (Sungkyunkwan University), S. Park (Samsung Electronics), L. Beloeil (France Telecom R&D), S. Madanapalli (iRam Technologies)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 18 mars 2017

Il existe deux méthodes pour configurer une machine IPv6 automatiquement, DHCP (RFC 3315) et RA (Router Advertisement, RFC 4862). Toutes les deux peuvent indiquer d'autres informations que l'adresse IP, comme par exemple les adresses des résolveurs DNS. Notre RFC normalise cette possibilité pour les RA. Il remplace le RFC 6106, avec peu de changements.

Si on gère un gros réseau, avec de nombreuses machines dont certaines, portables, vont et viennent, s'assurer que toutes ces machines ont les adresses IP des serveurs de noms à utiliser n'est pas trivial (section 1 du RFC). On ne peut évidemment pas utiliser le DNS, cela serait tenter de voler en tirant sur les lacets de ses chaussures. Et configurer à la main les adresses sur chaque machine (par exemple, sur Unix, en les écrivant dans le fichier /etc/resolv.conf) est bien trop difficile à maintenir. Se passer du DNS est hors de question. Pour les machines bi-protocoles (IPv4 et IPv6), une solution possible était d'utiliser un serveur de noms en v4. Mais pour une solution purement v6 ?

La solution la plus populaire était DHCP (RFC 3315 et RFC 3646). Son principal inconvénient est qu'elle est à état : le serveur DHCP doit se souvenir des baux qu'il a attribué. Sur un gros réseau local, le nombre de requêtes à traiter, chacune nécessitant une écriture dans une base de données, peut devenir très lourd.

Une autre solution est sans état et repose sur une nouveauté d'IPv6, les RA (Router Advertisements, cette méthode est aussi appelée ND, pour Neighbor Discovery, les RA en étant un cas particulier), décrits dans le RFC 4862. Ce sont des messages envoyés à intervalles réguliers par les routeurs et qui informent les machines non-routeuses des caractéristiques essentielles du réseau, comme le préfixe utilisé (par exemple 2001:db8:beef:42::/64). Le routeur diffuse ses messages et n'a pas besoin d'écrire quoi que ce soit sur son disque, ni de faire des traitements compliqués lors d'une sollicitation, il répond toujours par le même message RA.

Ces RA peuvent diffuser diverses informations, par le biais d'un système d'options. Le principe de notre RFC est donc d'utiliser ces RA pour transporter l'information sur les serveurs de noms récursifs utilisables sur le réseau local, via des options notamment celle nommée RDNSS (le numéro 25 lui a été affecté par l'IANA).

La section 1.1 du RFC rappelle qu'il existe plusieurs choix, notre RFC 8106 n'étant qu'une possibilité parmi d'autres. Le RFC 4339 contient une discussion plus détaillée de ce problème du choix d'une méthode de configuration des serveurs de noms (notons qu'il existe d'autres méthodes comme l'anycast avec une adresse « bien connue »). La section 1.2 décrit ce qui se passe lorsque plusieurs méthodes (par exemple DHCP et RA) sont utilisées en même temps.

La méthode RA décrite dans notre RFC repose sur deux options, RDNSS, déjà citée, et DNSSL (section 4). La première permet de publier les adresses des serveurs de noms, la seconde une liste de domaine à utiliser pour compléter les noms courts (formés d'un seul composant). Les valeurs pour ces deux options doivent être configurées dans le routeur qui va lancer les RA. (Le routeur Turris Omnia le fait automatiquement. Si on veut changer les paramètres, voici comment faire. En général, pour OpenWrt, il faut lire cette documentation, l'ancien logiciel radvd n'étant plus utilisé.)

La première option, RDNSS, de numéro 25, est décrite en section 5.1. Elle indique une liste d'adresse IPv6 que le client RA mettra dans sa liste locale de serveurs de noms interrogeables.

La seconde option, DNSSL, de numéro 31, est en section 5.2 (les deux options sont enregistrées dans le registre IANA, cf. section 8). Elle publie une liste de domaines, typiquement ceux qui, sur une machine Unix, se retrouveront dans l'option search de /etc/resolv.conf.

Sur Linux, le démon rdnssd permet de recevoir ces RA et de modifier la configuration DNS. Pour FreeBSD, on peut consulter une discussion sur leur liste. Les CPE de Free, les Freebox, émettent de telles options dans leurs RA (apparemment, la dernière fois que j'ai regardé, uniquement des RDNSS). Voici ce qu'affiche Wireshark :

Ethernet II, Src: FreeboxS_c3:83:23 (00:07:cb:c3:83:23), 
             Dst: IPv6mcast_00:00:00:01 (33:33:00:00:00:01)
Internet Control Message Protocol v6
    Type: 134 (Router advertisement)
    ICMPv6 Option (Recursive DNS Server)
        Type: Recursive DNS Server (25)
        Length: 40
        Lifetime: 600
        Recursive DNS Servers: 2a01:e00::2 (2a01:e00::2)
        Recursive DNS Servers: 2a01:e00::1 (2a01:e00::1)

et les serveurs DNS annoncés répondent correctement. (Vous pouvez récupérer le paquet entier sur

Autre mise en œuvre de ces options, dans radvd (ainsi que pour les logiciels auxiliaires). Wireshark, on l'a vu, sait décoder ces options.

La section 6 de notre RFC donne des conseils aux programmeurs qui voudraient mettre en œuvre ce document. Par exemple, sur un système d'exploitation où le client RA tourne dans le noyau (pour configurer les adresses IP) et où la configuration DNS est dans l'espace utilisateur, il faut prévoir un mécanisme de communication, par exemple un démon qui interroge le noyau régulièrement pour savoir s'il doit mettre à jour la configuration DNS.

RA pose divers problèmes de sécurité, tout comme DHCP, d'ailleurs. Le problème de ces techniques est qu'elles sont conçues pour faciliter la vue de l'utilisateur et de l'administrateur réseau et que « faciliter la vie » implique en général de ne pas avoir de fonctions de sécurité difficiles à configurer. La section 7 traite de ce problème, par exemple du risque de se retrouver avec l'adresse d'un serveur DNS méchant qui vous redirigerait Dieu sait où (les RA ne sont pas authentifiés). Ce risque n'a rien de spécifique aux options DNS, toute la technique RA est vulnérable (par exemple, avec un faux Neighbor Advertisement). Donc, notre RFC n'apporte pas de risque nouveau (cf. RFC 6104). Si on considère cette faiblesse de sécurité comme insupportable, la section 7.2 recommande d'utiliser le RA guard du RFC 6105, ou bien SEND (RFC 3971, mais il est nettement moins mis en avant que dans le précédent RFC).

Ce problème d'une auto-configuration simple des machines connectées à IPv6 est évidemment particulièrement important pour les objets connectés et c'est sans doute pour cela que le RFC contient la mention « This document was supported by Institute for Information & communications Technology Promotion (IITP) grant funded by the Korea government (MSIP) [10041244, Smart TV 2.0 Software Platform] ».

Les changements faits depuis le précédent RFC, le RFC 6106, figurent dans l'annexe A. On y trouve notamment :

  • Une valeur par défaut plus élevée pour la durée de vie des informations envoyées (qui passe de deux fois MaxRtrAdvInterval à trois fois sa valeur, soit 1 800 secondes avec la valeur par défaut de cette variable), pour diminuer le nombre de cas où l'information expire parce que le réseau perdait trop de paquets,
  • L'autorisation explicite des adresses locales au lien (celles en fe80::/10), comme adresses de résolveurs DNS,
  • Suppression de la limite de trois résolveurs DNS, qui était dans l'ancien RFC.

À noter que ce RFC n'intègre pas encore les résolveurs sécurisés du RFC 7858, car il se contente de réviser un RFC existant. Il n'y a donc pas de moyen de spécifier un résolveur sécurisé, pas de port 853.

Et pour finir, voici le RA émis par défaut par le routeur Turris, décodé par Wireshark :

Internet Protocol Version 6, Src: fe80::da58:d7ff:fe00:4c9e, Dst: ff02::1
    0110 .... = Version: 6
    .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00 (DSCP: CS0, ECN: Not-ECT)
        .... 0000 00.. .... .... .... .... .... = Differentiated Services Codepoint: Default (0)
        .... .... ..00 .... .... .... .... .... = Explicit Congestion Notification: Not ECN-Capable Transport (0)
    .... .... .... 0101 1110 1011 0100 0001 = Flow label: 0x5eb41
    Payload length: 152
    Next header: ICMPv6 (58)
    Hop limit: 255
    Source: fe80::da58:d7ff:fe00:4c9e
    [Source SA MAC: CzNicZSP_00:4c:9e (d8:58:d7:00:4c:9e)]
    Destination: ff02::1
    [Source GeoIP: Unknown]
    [Destination GeoIP: Unknown]
Internet Control Message Protocol v6
    Type: Router Advertisement (134)
    Code: 0
    Checksum: 0x35ed [correct]
    [Checksum Status: Good]
    Cur hop limit: 64
    Flags: 0x80
        1... .... = Managed address configuration: Set
        .0.. .... = Other configuration: Not set
        ..0. .... = Home Agent: Not set
        ...0 0... = Prf (Default Router Preference): Medium (0)
        .... .0.. = Proxy: Not set
        .... ..0. = Reserved: 0
    Router lifetime (s): 1800
    Reachable time (ms): 0
    Retrans timer (ms): 0
    ICMPv6 Option (Source link-layer address : d8:58:d7:00:4c:9e)
        Type: Source link-layer address (1)
        Length: 1 (8 bytes)
        Link-layer address: CzNicZSP_00:4c:9e (d8:58:d7:00:4c:9e)
    ICMPv6 Option (MTU : 1480)
        Type: MTU (5)
        Length: 1 (8 bytes)
        MTU: 1480
    ICMPv6 Option (Prefix information : fde8:9fa9:1aba::/64)
        Type: Prefix information (3)
        Length: 4 (32 bytes)
        Prefix Length: 64
        Flag: 0xc0
            1... .... = On-link flag(L): Set
            .1.. .... = Autonomous address-configuration flag(A): Set
            ..0. .... = Router address flag(R): Not set
            ...0 0000 = Reserved: 0
        Valid Lifetime: 7200
        Preferred Lifetime: 1800
        Prefix: fde8:9fa9:1aba::
    ICMPv6 Option (Prefix information : 2a01:e35:8bd9:8bb0::/64)
        Type: Prefix information (3)
        Length: 4 (32 bytes)
        Prefix Length: 64
        Flag: 0xc0
            1... .... = On-link flag(L): Set
            .1.. .... = Autonomous address-configuration flag(A): Set
            ..0. .... = Router address flag(R): Not set
            ...0 0000 = Reserved: 0
        Valid Lifetime: 7200
        Preferred Lifetime: 1800
        Prefix: 2a01:e35:8bd9:8bb0::
    ICMPv6 Option (Route Information : Medium fde8:9fa9:1aba::/48)
        Type: Route Information (24)
        Length: 3 (24 bytes)
        Prefix Length: 48
        Flag: 0x00
            ...0 0... = Route Preference: Medium (0)
            000. .000 = Reserved: 0
        Route Lifetime: 7200
        Prefix: fde8:9fa9:1aba::
    ICMPv6 Option (Recursive DNS Server fde8:9fa9:1aba::1)
        Type: Recursive DNS Server (25)
        Length: 3 (24 bytes)
        Lifetime: 1800
        Recursive DNS Servers: fde8:9fa9:1aba::1
    ICMPv6 Option (Advertisement Interval : 600000)
        Type: Advertisement Interval (7)
        Length: 1 (8 bytes)
        Advertisement Interval: 600000

On y voit l'option RDNSS (l'avant-dernière) mais pas de DNSSL.

Merci à Alexis La Goutte pour ses informations.

Téléchargez le RFC 8106

L'article seul

RFC 8109: Initializing a DNS Resolver with Priming Queries

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : P. Koch (DENIC), M. Larson, P. Hoffman (ICANN)
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 16 mars 2017

Un résolveur DNS ne connait au début, rien du contenu du DNS. Rien ? Pas tout à fait, il connait une liste des serveurs de noms faisant autorité pour la racine, car c'est par eux qu'il va commencer le processus de résolution de noms. Cette liste est typiquement en dur dans le code du serveur, ou bien dans un de ses fichiers de configuration. Mais peu d'administrateurs système la maintiennent à jour. Il est donc prudent, au démarrage du résolveur, de chercher une liste vraiment à jour, et c'est le priming (initialisation ?), opération que décrit ce RFC.

Le problème de départ d'un résolveur est un problème d'œuf et de poule. Le résolveur doit interroger le DNS pour avoir des informations mais comment trouve-t-il les serveurs DNS à interroger ? La solution est de traiter la racine du DNS de manière spéciale : la liste de ses serveurs est connue du résolveur au démarrage. Elle peut être dans le code du serveur lui-même, ici un Unbound qui contient les adresses IP des serveurs de la racine (je ne montre que les trois premiers,, et :

% strings /usr/sbin/unbound | grep -i 2001:       

Ou bien elle est dans un fichier de configuration (ici, sur un Unbound) :

  directory: "/etc/unbound"
  root-hints: "root-hints"

Ce fichier peut être téléchargé via l'IANA, il peut être spécifique au logiciel résolveur, ou bien fourni par le système d'exploitation (cas du paquetage dns-root-data chez Debian). Il contient la liste des serveurs de la racine et leurs adresses :

.                        3600000      NS    A.ROOT-SERVERS.NET.
.                        3600000      NS    B.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET.      3600000      A
A.ROOT-SERVERS.NET.      3600000      AAAA  2001:503:ba3e::2:30
B.ROOT-SERVERS.NET.      3600000      A
B.ROOT-SERVERS.NET.      3600000      AAAA  2001:500:84::b

Cette configuration initiale du résolveur est décrite dans la section 2.3 du RFC 1034, mais ce dernier ne décrit pas réellement le priming (quoi que dise notre nouveau RFC), priming que tous les résolveurs actuels mettent en œuvre. En effet, les configurations locales tendent à ne plus être à jour au bout d'un moment. (Sauf dans le cas où elles sont dans un paquetage du système d'exploitation, mis à jour avec ce dernier, comme dans le bon exemple Debian ci-dessus.)

Les changements des serveurs racines sont rares. Si on regarde sur le site des opérateurs des serveurs racine, on voit :

  • 2016-12-02 Announcement of IPv6 addresses
  • 2015-11-05 L-Root IPv6 Renumbering
  • 2015-08-31 H-Root to be renumbered
  • 2014-03-26 IPv6 service address for (2001:500:2::C)
  • 2012-12-14 D-Root IPv4 Address to be Renumbered
  • 2011-06-10 IPv6 service address for (2001:500:2D::D)

Bref, peu de changements. Ils sont en général annoncés sur les listes de diffusion opérationnelles (comme ici, ou encore ici). Mais les fichiers de configuration ayant une fâcheuse tendance à ne pas être mis à jour et à prendre de l'âge, les anciennes adresses des serveurs racine continuent à recevoir du trafic des années après (comme le montre cette étude de J-root). Notez que la stabilité de la liste des serveurs racine n'est pas due qu'au désir de ne pas perturber les administrateurs système : il y a aussi des raisons politiques (aucun mécanisme en place pour choisir de nouveaux serveurs, ou pour retirer les « maillons faibles »). C'est pour cela que la liste des serveurs (mais pas leurs adresses) n'a pas changé depuis 1997 !

Notons aussi que l'administrateur système d'un résolveur peut changer la liste des serveurs de noms de la racine pour une autre liste. C'est ainsi que fonctionnent les racines alternatives comme Yeti. Si on veut utiliser cette racine expérimentale et pas la racine « officielle », on édite la configuration de son résolveur :

    root-hints: "yeti-hints"

Et le fichier, téléchargé chez Yeti, contient :

.                              3600000    IN   NS                                3600000    IN   AAAA     240c:f:1:22::6                          
.                              3600000    IN   NS                              3600000    IN   AAAA     2001:559:8000::6                        
.                              3600000    IN   NS                          3600000    IN   AAAA     2001:200:1d9::35                        
.                              3600000    IN   NS                     

Le priming, maintenant. Le principe du priming est, au démarrage, de faire une requête à un des serveurs listés dans la configuration et de garder sa réponse (certainement plus à jour que la configuration) :

% dig +bufsize=4096 +norecurse +nodnssec NS .

; <<>> DiG 9.10.3-P4-Debian <<>> +norecurse +nodnssec NS .
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42123
;; flags: qr aa; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 27

; EDNS: version: 0, flags:; udp: 4096
;.			IN NS

.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS
.			518400 IN NS

;; ADDITIONAL SECTION:	518400 IN A	518400 IN AAAA 2001:503:ba3e::2:30	518400 IN A	518400 IN AAAA 2001:500:84::b	518400 IN A	518400 IN AAAA 2001:500:2::c	518400 IN A	518400 IN AAAA 2001:500:2d::d	518400 IN A	518400 IN AAAA 2001:500:a8::e	518400 IN A	518400 IN AAAA 2001:500:2f::f	518400 IN A	518400 IN AAAA 2001:500:12::d0d	518400 IN A	518400 IN AAAA 2001:500:1::53	518400 IN A	518400 IN AAAA 2001:7fe::53	518400 IN A	518400 IN AAAA 2001:503:c27::2:30	518400 IN A	518400 IN AAAA 2001:7fd::1	518400 IN A	518400 IN AAAA 2001:500:9f::42	518400 IN A	518400 IN AAAA 2001:dc3::35

;; Query time: 3 msec
;; SERVER: 2001:7fd::1#53(2001:7fd::1)
;; WHEN: Fri Mar 03 17:29:05 CET 2017
;; MSG SIZE  rcvd: 811

(Les raisons du choix des trois options données à dig sont indiquées plus loin.)

La section 3 de notre RFC décrit en détail à quoi ressemblent les requêtes de priming. Le type de données demandé (QTYPE) est NS (Name Servers, type 2) et le nom demandé (QNAME) est « . » (oui, juste la racine). D'où le dig NS . ci-dessus. Le bit RD (Recursion Desired) est typiquement mis à zéro (d'où le +norecurse dans l'exemple avec dig). La taille de la réponse dépassant les 512 octets (limite très ancienne du DNS), il faut utiliser EDNS (cause du +bufsize=4096 dans l'exemple). On peut utiliser le bit DO (DNSSEC OK) qui indique qu'on demande les signatures DNSSEC mais ce n'est pas habituel (d'où le +nodnssec dans l'exemple). En effet, si la racine est signée, permettant d'authentifier l'ensemble d'enregistrements NS, la zone, où se trouvent actuellement tous les serveurs de la racine, ne l'est pas, et les enregistrements A et AAAA ne peuvent donc pas être validés avec DNSSEC.

Cette requête de priming est envoyée lorsque le résolveur démarre, et aussi lorsque la réponse précédente a expiré (regardez le TTL dans l'exemple : six jours). Si le premier serveur testé ne répond pas, on essaie avec un autre. Ainsi, même si le fichier de configuration n'est pas parfaitement à jour (des vieilles adresses y trainent), le résolveur finira par avoir la liste correcte.

Et comment choisit-on le premier serveur qu'on interroge ? Notre RFC recommande un tirage au sort, pour éviter que toutes les requêtes de priming ne se concentrent sur un seul serveur (par exemple le premier de la liste). Une fois que le résolveur a démarré, il peut aussi se souvenir du serveur le plus rapide, et n'interroger que celui-ci, ce qui est fait par la plupart des résolveurs, pour les requêtes ordinaires (mais n'est pas conseillé pour le priming).

Et les réponses au priming ? Il faut bien noter que, pour le serveur racine, les requêtes priming sont des requêtes comme les autres, et ne font pas l'objet d'un traitement particulier. Normalement, la réponse doit avoir le code de retour NOERROR (c'est bien le cas dans mon exemple). Parmi les flags, il doit y avoir AA (Authoritative Answer). La section de réponse doit évidemment contenir les NS de la racine, et la section additionnelle les adresses IP. Le résolveur garde alors cette réponse dans son cache, comme il le ferait pour n'importe quelle autre réponse. Notez que là aussi, il ne faut pas de traitement particulier. Par exmeple, le résolveur ne doit pas compter qu'il y aura exactement 13 serveurs, même si c'est le cas depuis longtemps (ça peut changer).

Normalement, le serveur racine envoie la totalité des adresses IP (deux par serveur, une en IPv4 et une en IPv6). S'il ne le fait pas (par exemple par manque de place parce qu'on a bêtement oublié EDNS), le résolveur va devoir envoyer des requêtes A et AAAA explicites pour obtenir les adresses IP :

% dig A 

; <<>> DiG 9.10.3-P4-Debian <<>> A
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49091
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 26
;; WARNING: recursion requested but not available

; EDNS: version: 0, flags: do; udp: 4096
;	IN A



Vous pouvez voir ici les requêtes et réponses de priming d'un Unbound utilisant Yeti. D'abord, décodées par tcpdump :

20:31:36.226325 IP6 2001:4b98:dc2:43:216:3eff:fea9:41a.7300 > 2a02:cdc5:9715:0:185:5:203:53.53: 50959% [1au] NS? . (28)
20:31:36.264584 IP6 2a02:cdc5:9715:0:185:5:203:53.53 > 2001:4b98:dc2:43:216:3eff:fea9:41a.7300: 50959*- 26/0/7 NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS, NS xn--r2bi1c.xn--h2bv6c0a.xn--h2brj9c., NS, NS, NS, NS, RRSIG (1225)

Et ici par tshark :

1   0.000000 2001:4b98:dc2:43:216:3eff:fea9:41a → 2a02:cdc5:9715:0:185:5:203:53 DNS 90 Standard query 0xc70f NS <Root> OPT
2   0.038259 2a02:cdc5:9715:0:185:5:203:53 → 2001:4b98:dc2:43:216:3eff:fea9:41a DNS 1287 Standard query response 0xc70f NS <Root> NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS NS xn--r2bi1c.xn--h2bv6c0a.xn--h2brj9c NS NS NS NS RRSIG AAAA 240c:f:1:22::6 AAAA 2a01:4f8:161:6106:1::10 AAAA 2001:e30:1c1e:1:

Et un décodage plus détaillé de tshark dans ce fichier.

Enfin, la section 5 de notre RFC traite des problèmes de sécurité du priming. Évidemment, si un attaquant injecte une fausse réponse aux requêtes de priming, il pourra détourner toutes les requêtes ultérieures vers des machines de son choix. À part le RFC 5452, la seule protection est DNSSEC : si le résolveur valide (et a donc la clé publique de la racine), il pourra détecter que les réponses sont mensongères. Cela a l'avantage de protéger également contre d'autres attaques, ne touchant pas au priming, comme les attaques sur le routage.

Notez que DNSSEC est recommandé pour valider les réponses ultérieures mais, comme on l'a vu, n'est pas important pour valider la réponse de priming elle-même, puisque n'est pas signé. Si un attaquant détournait, d'une manière ou d'une autre, vers un faux serveur racine, servant de fausses données, ce ne serait qu'une attaque par déni de service, puisque le résolveur validant pourrait détecter que les réponses sont fausses.

Ce RFC a connu une très longue gestation puisque le premier brouillon date de février 2007 (vous pouvez admirer la chronologie).

Téléchargez le RFC 8109

L'article seul

RFC 8117: Current Hostname Practice Considered Harmful

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : C. Huitema (Private Octopus Inc.), D. Thaler (Microsoft), R. Winter (University of Applied Sciences Augsburg)
Pour information
Réalisé dans le cadre du groupe de travail IETF intarea
Première rédaction de cet article le 12 mars 2017

« Je suis l'iPhone de Jean-Luc ! » Traditionnellement, les ordinateurs connectés à l'Internet ont un nom, et ce nom est souvent annoncé à l'extérieur par divers protocoles. Cette pratique très répandue, dont l'origine remonte à l'époque où on n'avait que quelques gros serveurs partagés, et fixes, est dangereuse pour la vie privée, dans un monde de mobilité et de machines individuelles. Comme le note ce nouveau RFC, « c'est comme si on se promenait dans la rue avec une étiquette bien visible portant son nom ». Ce RFC dresse l'état des lieux, fait la liste des protocoles problématiques, et suggère, lorsqu'on ne peut pas changer le protocole, d'utiliser des noms aléatoires, ne révélant rien sur la machine.

Pour illustrer le problème, voici un exemple du trafic WiFi pendant une réunion, en n'écoutant qu'un seul protocole, mDNS (RFC 6762). Et d'autres protocoles sont tout aussi bavards. Notez que cette écoute n'a nécessité aucun privilège particulier sur le réseau, ni aucune compétence. N'importe quel participant à la réunion, ou n'importe quelle personne située à proximité pouvait en faire autant avec tcpdump (j'ai changé les noms des personnes) :

% sudo tcpdump -n -vvv port 5353
tcpdump: listening on wlp2s0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:03:16.909436 IP6 fe80::86a:ed2c:1bcc:6540.5353 > ff02::fb.5353: 0*- [0q] 2/0/3 (Cache flush) [2m] PTR John-Smiths-iPhone-7.local., [...]
15:03:17.319992 IP > 0*- [0q] 2/0/3 (Cache flush) [2m] PTR Jane-iPhone.local., [...]
15:03:20.699557 IP6 fe80::e2ac:cbff:fe95:da80.5353 > ff02::fb.5353: 0 [5q] [4n] [1au] PTR (QU)? _googlecast._tcp.local. ANY (QU)? info-mac-66._smb._tcp.local. [...]

On y voit que les noms des machines présentes sont annoncés à tous (ff02::fb et sont des adresses multicast). Certains noms sont très révélateurs (nom, prénom et type de la machine), d'autres un peu moins (prénom et type), d'autres sont presques opaques (juste un type de machine, très général). Un indiscret qui regarde le trafic sur des réseaux publiquement accessibles peut ainsi se faire une bonne idée de quelles machines sont présentes, voire de qui est présent. Les deux exemples des noms info-mac-66 et John-Smiths-iPhone-7 illustrent les deux risques. Dans le premier cas, si le nom est stable, il permet de suivre à la trace une machine qui se déplacerait. Le second cas est encore pire puisqu'on a directement le nom du propriétaire.

Le fait que les ordinateurs aient des noms est une tradition très ancienne (voir la définition de host name dans le RFC 7719). Un nom court (sans point à l'intérieur) combiné avec un suffixe forme un FQDN (Fully Qualified Domain Name, cf. RFC 1983). On utilise ces noms courts et ces FQDN à plein d'endroits. IP lui-même n'utilise pas de noms du tout mais plein de protocoles de la famille TCP/IP le font, comme mDNS montré plus haut.

Un nom court doit être unique dans un contexte donné mais n'est pas forcément unique mondialement. Le FQDN, lui, est unique au niveau mondial.

Je vous recommande l'excellent travail de M. Faath, F. Weisshaar et R. Winter, « How Broadcast Data Reveals Your Identity and Social Graph » à l'atelier TRAC 2016 (supports de leur exposé), montrant toutes les fuites d'information liées à cette utilisation des noms, et ce qu'un méchant peut en faire. (C'est ce groupe qui avait écouté le trafic WiFi lors d'une réunion IETF à Prague, déclenchant une grande discussion sur les attentes en matière de vie privée quand le trafic est diffusé.)

Pourquoi nomme-t-on les ordinateurs, au fait, à part la tradition ? Sur un réseau, bien des systèmes d'exploitation, à commencer par Unix et Windows tiennent pour acquis que les ordinateurs ont un nom, et ce nom peut être utilisé dans des tas de cas. Il existe plusieurs schémas de nommage (section 2 du RFC), du plus bucolique (noms de fleurs) au plus français (noms de vins) en passant par les schémas bien geeks comme les noms des personnages du Seigneur des Anneaux. Mais, parfois, c'est le système d'exploitation lui-même qui nomme l'ordinateur, en combinant le nom de l'utilisateur et les caractéristiques de l'ordinateur, comme on le voit avec les iPhones dans l'exemple tcpdump ci-dessus. (Sur les schémas de nommage, voir le RFC 1178, et, sur un ton plus léger, le RFC 2100. Il existe une excellente page Web pleine d'idées de noms. L'ISC fait des statistiques sur les noms vus sur Internet. Entre 1995 et 2017, vous pouvez constater la décroissance des noms sympas en faveur des noms utilitaires.)

Dans les environnements corporate, on ne laisse pas l'utilisateur choisir et il y a un schéma officiel. Par exemple, sur le réseau interne de Microsoft, le nom est dérivé du nom de login de l'utilisateur et un des auteurs du RFC a donc une machine huitema-test-2.

Est-il nécessaire de donner des noms aux « objets », ces machines à laver ou brosses à dents connectés, qui sont des ordinateurs, mais ne sont en général pas perçus comme tels (ce qui a des graves conséquences en terme de sécurité) ? Comme ces engins n'offrent en général pas de services, ils ont moins besoin d'un nom facile à retenir, et, lorsque les protocoles réseaux employés forcent à utiliser un nom, c'est également un nom fabriqué à partir du nom du fabricant, du modèle de l'appareil et de son numéro de série (donc, un nom du genre BrandX-edgeplus-4511-2539). On voit même parfois la langue parlée par l'utilisateur utilisée dans ce nom, qui est donc très « parlant ».

Même un identificateur partiel peut être révélateur (section 3 du RFC). Si on ordinateur se nomme dthaler-laptop, on ne peut pas être sûr qu'il appartienne vraiment au co-auteur du RFC Dave Thaler. Il y a peut-être d'autres D. Thaler dans le monde. Mais si on observe cet ordinateur faire une connexion au réseau interne de Microsoft (pas besoin de casser le chiffrement, les métadonnées suffisent), on est alors raisonnablement sûr qu'on a identifié le propriétaire.

Beaucoup de gens croient à tort qu'un identificateur personnel doit forcément inclure le nom d'état civil de l'utilisateur. Mais ce n'est pas vrai : il suffit que l'identificateur soit stable, et puisse être relié, d'une façon ou d'une autre, au nom de l'utilisateur. Par exemple, si un ordinateur portable a le nom stable a3dafaaf70950 (nom peu parlant) et que l'observateur ait pu voir une fois cette machine faire une connexion à un compte IMAP jean_dupont, on peut donc associer cet ordinateur à Jean Dupont, et le suivre ensuite à la trace.

Ce risque est encore plus important si l'attaquant maintient une base de données des identifications réussies (ce qui est automatisable), et des machines associées. Une ou deux fuites d'information faites il y a des mois, voire des années, et toutes les apparitions ultérieures de cette machine mèneront à une identification personnelle.

Donc, n'écoutez pas les gens qui vous parleront d'« anonymat » parce que les noms de machine ne sont pas parlants (comme le a3dafaaf70950 plus haut). Si quelqu'un fait cela, cela prouve simplement qu'il ne comprend rien à la sécurité informatique. Un nom stable, pouvant être observé (et on a vu que bien des protocoles étaient très indiscrets), permet l'observation, et donc la surveillance.

Justement, quels sont les protocoles qui laissent ainsi fuiter des noms de machine, que l'observateur pourra noter et enregistrer (section 4 du RFC) ? Il y a d'abord DHCP, où le message de sollicitation initial (diffusé à tous…) contient le nom de la machine en clair. Le problème de vie privée dans DHCP est analysé plus en détail dans les RFC 7819 et RFC 7824. Les solutions pour limiter les dégâts sont dans le RFC 7844.

Le DNS est également une cause de fuite, par exemple parce qu'il permet d'obtenir le nom d'une machine à partir de son adresse IP, avec les requêtes PTR dans ou, nom qui peut réveler des détails. C'est le cas avec tout protocole conçu justement pour distribuer des informations, comme celui du RFC 4620 (qui ne semble pas très déployé dans la nature).

Plus sérieux est le problème de mDNS (RFC 6762), illustré par le tcpdump montré plus haut. Les requêtes sont diffusées à tous sur le réseau local, et contiennent, directement ou indirectement, les noms des machines. Même chose avec le DNS Service Discovery du RFC 6763 et le LLMNR du RFC 4795 (beaucoup moins fréquent que mDNS).

Enfin, NetBIOS (quelqu'un l'utilise encore ?) est également une grande source d'indiscrétions.

Assez décrit le problème, comment le résoudre (section 5) ? Bien sûr, il faudra des protocoles moins bavards, qui ne clament pas le nom de la machine à tout le monde. Mais changer d'un coup des protocoles aussi répandus et aussi fermement installés que, par exemple, DHCP, ne va pas être facile. De même, demander aux utilisateurs de ne pas faire de requêtes DHCP lorsqu'ils visitent un réseau « non sûr » est difficile (déjà, comment l'utilisateur va-t-il correctement juger si le réseau est sûr ?), d'autant plus qu'ils risquent fort de ne pas avoir de connectivité du tout, dans ce cas. Certes, couper les protocoles non nécessaires est un bon principe de sécurité en général. Mais cet angle d'action semble quand même bien trop drastique. (Il faut aussi noter qu'il existe des protocoles privés, non-IETF, qui peuvent faire fuire des noms sans qu'on le sache. Le client Dropbox diffuse à la cantonade l'ID du client, et celui des shares où il se connecte. Il est facile de faire un graphe des utilisateurs en mettant ensemble ceux qui se connectent au même share.)

La suggestion de notre RFC est donc d'attaquer le problème d'une autre façon, en changeant le nom de la machine, pour lui substituer une valeur imprévisible (comme le fait le RFC 7844 pour les adresses MAC). Pour chaque nouveau réseau où est connectée la machine, on génère aléatoirement un nouveau nom, et c'est celui qu'on utilisera dans les requêtes DHCP ou mDNS. Ces protocoles fonctionneront toujours mais la surveillance des machines mobiles deviendra bien plus difficile. Bien sûr, pour empêcher toute corrélation, le changement de nom doit être coordonné avec les changements des autres identificateurs, comme l'adresse IP ou l'adresse MAC.

Windows a même un concept de « nom de machine par réseau », ce qui permet aux machines ayant deux connexions de présenter deux identités différentes (malheureusement, Unix n'a pas ce concept, le nom est forcément global).

Bien sûr, on n'a rien sans rien (section 6). Si on change les noms des machines, on rendra l'administration système plus difficile. Par exemple, l'investigation sur un incident de sécurité sera plus complexe. Mais la défense de la vie privée est à ce prix.

Pour l'instant, à ma connaissance, il n'y a pas encore de mise en œuvre de cette idée de noms imprévisibles et changeants. (Une proposition a été faite pour Tails. Notez qu'il existe d'autres possibilités comme d'avoir un nom unique partout.)

Téléchargez le RFC 8117

L'article seul

RFC 8078: Managing DS records from the Parent via CDS/CDNSKEY

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : O. Gudmundsson (CloudFlare), P. Wouters (Red Hat)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 11 mars 2017

Un problème fréquent avec DNSSEC est de transmettre à sa zone parente les clés publiques de signature de sa zone, pour que le parent puisse signer un lien qui va vers ces clés (l'enregistrement de type DS). Le RFC 7344 apportait une solution partielle, avec ses enregistrements CDS et CDNSKEY. Il y manquait deux choses : la création du premier DS (activation initiale de DNSSEC), et le retrait de tout les DS (on arrête de faire du DNSSEC). Ce nouveau RFC 8078 comble ces deux manques (et, au passage, change l'état du RFC 7344, qui passe sur le Chemin des Normes).

Avant le RFC 7344, tout changement des clés KSK (Key Signing Key) d'une zone nécessitait une interaction avec la zone parente, par le biais d'un mécanisme non-DNS (« out-of-band », par exemple un formulaire Web). La solution du RFC 7344, elle, n'utilise que le DNS (« in-band »). Ce nouveau RFC complète le RFC 7344 pour les configurations initiales et finales. (Le problème est complexe car il peut y avoir beaucoup d'acteurs en jeu. Par exemple, le BE n'est pas forcément l'hébergeur DNS. Ces difficultés ont certainement nui au déploiement de DNSSEC.)

Lorsqu'on change d'hébergeur DNS, la solution la plus propre est de faire un remplacement des clés, depuis celle de l'ancien hébergeur jusqu'à celle du nouveau. Cette solution préserve en permanence la sécurité qu'offre DNSSEC. Mais une des procédures mentionnées par notre RFC passe au contraire par un état non sécurisé, où la zone n'est pas signée. C'est dommage mais cela est parfois nécessaire si :

  • Les logiciels utilisés ne permettent pas de faire mieux, ou l'un des deux hébergeurs ne veut pas suivre la procédure « propre »,
  • Ou bien le nouvel hébergeur ne gère pas DNSSEC du tout, ou encore le titulaire de la zone ne veut plus de DNSSEC.

Une zone non signée vaut certainement mieux qu'une signature invalide. Mais le RFC oublie de dire que cela va casser certaines applications de sécurité qui exigent DNSSEC comme DANE (RFC 6698) ou SSHFP (RFC 4255).

Avant de lire la suite de ce RFC, deux conseils :

  • Lisez bien le RFC 7344. Vraiment.
  • Rappelez-vous qu'il y a des tas d'acteurs possibles dans le DNS. Le modèle RRR (Titulaire-BE-Registre, Registrant-Registrar-Registry) n'est pas le seul. Et il n'y a pas que les TLD qui délèguent des zones ! Le RFC parle donc uniquement de « parent » (responsable parental ?) pour désigner l'entité à laquelle on s'adresse pour obtenir des changements dans la zone parente.

Les enregistrements CDS (Client-side Delegation Signer) servent à trois choses (section 2 du RFC) :

  • Installer le DS (Delegation Signer) initial dans la zone parente,
  • Remplacer (rollover) la clé publique de signature des clés (KSK, Key-Signing Key) dans la zone parente,
  • Supprimer le DS de la zone parente, débrayant ainsi la validation DNSSEC de la zone fille chez les résolveurs.

Avec le RFC 7344, seule la deuxième était possible (c'est la moins dangereuse, qui ne nécessite aucun changement dans les relations de confiance,notamment entre parente et fille). Notre RFC 8078 permet désormais les deux autres, plus délicates, car posant davantage de problèmes de sécurité.

La sémantique des enregistrements CDS (ou CDNSKEY) est donc désormais « la publication d'un ou plusieurs CDS indique un souhait de synchronisation avec la zone parente ; celle-ci est supposée avoir une politique en place pour accepter/refuser/vérifier ce ou ces CDS, pour chacune des trois utilisations notées ci-dessus ». Quand des CDS différents des DS existants apparaissent dans la zone fille, le responsable parental doit agir.

D'abord, l'installation initiale d'un DS alors qu'il n'y en avait pas avant (section 3 du RFC). La seule apparition du CDS ou du CDNSKEY ne peut pas suffire car comment le vérifier, n'ayant pas encore de chaîne DNSSEC complète ? Le responsable parental peut utiliser les techniques suivantes :

  • Utiliser un autre canal, extérieur au DNS, par exemple l'API du responsable parental,
  • Utiliser des tests de vraisemblance, du genre un message de confirmation envoyé au contact technique du domaine, ou bien regarder si la configuration du domaine est stable,
  • Attendre un certain temps, de préférence vérifier depuis plusieurs endroits dans le réseau (pour éviter les empoisonnements locaux), puis considérer le CDS comme valable s'il est resté pendant ce temps (l'idée est qu'un piratage aurait été détecté, pendant ce délai),
  • Envoyer un défi au titulaire de la zone fille, par exemple génerer une valeur aléatoire et lui demander de l'insérer sous forme d'un enregistrement TXT dans la zone (bien des applications qui veulent vérifier le responsable d'un domaine font cela, par exemple Keybase ou bien Google webmasters),
  • Accepter immédiatement s'il s'agit d'une nouvelle délégation. Ainsi, le domaine sera signé et validable dès le début.

La deuxième utilisation des CDS, remplacer une clé est, on l'a vu, déjà couverte par le RFC 7344.

Et pour la troisième utilisation, la suppression de tous les DS chez le parent ? Elle fait l'objet de la section 4 du RFC. Pour demander cette suppression, on publie un CDS (ou un CDNSKEY) avec un champ « algorithme » à zéro. Cette valeur n'est pas affectée à un vrai algorithme dans le registre officiel, elle est réservée (cf. section 6 du RFC) pour dire « efface ». (Le RFC 4398 utilisait déjà le même truc.)

Pour éviter tout accident, le RFC est plus exigeant que cela et exige cette valeur spécifique pour ces enregistrements :


ou bien :


(Le 3 étant l'actuel numéro de version de DNSSEC, voir le RFC 4034, section 2.1.2.)

Une fois le CDS (ou CDNSKEY) « zéro » détecté, et validé par DNSSEC, le parent retire le DS. Une fois le TTL passé, le fils peut « dé-signer » la zone.

À noter que ce RFC a été retardé par la question du déplacement du RFC 7344, de son état « pour information », au Chemin des Normes. La demande était discrète, et avait été raté par certains relecteurs, qui ont protesté ensuite contre ce « cavalier ». L'« élévation » du RFC 7344 est désormais explicite.

Téléchargez le RFC 8078

L'article seul

RFC 8128: IETF Appointment Procedures for the ICANN Root Zone Evolution Review Committee

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : C. Morgan (AMS)
Pour information
Première rédaction de cet article le 11 mars 2017

Un petit RFC purement bureaucratique publié hier, il décrit les procédures par lesquelles l'IETF nomme un représentant dans un des innombrables comités de l'ICANN, le RZERC (Root Zone Evolution Review Committee), qui travaille sur la gestion de la zone racine du DNS.

Ce nouveau comité RZERC est chargé des mécanismes de publication de la zone racine, une zone évidemment cruciale puisque la nature arborescente du DNS fait que, si elle a des problèmes, plus rien ne marche. Notez que le RZERC ne s'occupe que de la création et de la publication de la zone racine, pas de servir cette zone. Cette tâche incombe en effet aux serveurs racines du DNS, qui sont indépendants de l'ICANN (contrairement à ce qu'on lit souvent dans des médias mal informés). L'actuelle charte du RZERC est en ligne et elle prévoit que le comité comprend entre autres « The Chair or delegate of the Internet Engineering Task Force ».

C'est l'IAB qui désigne le représentant IETF, le premier étant Jim Reid. Les qualités nécessaires sont citées en section 2 de notre RFC. Sans surprise, il faut être techniquement très compétent, et il faut pouvoir traduire des recommandations en des termes compréhensibles par la bureaucratie ICANN (« be able to articulate those technology issues such that the ICANN Board can be provided with sound technical perspectives »). Le RFC précise également qu'il faut comprendre l'articulation de la gouvernance Internet et les rôles des différents organismes, une tâche complexe, c'est sûr !

Suivant les procédures décrites en section 3 du RFC, un appel à volontaires avait été lancé le 25 mai 2016, il y avait quatre candidats (Marc Blanchet, Warren Kumari, Kaveh Ranjbar et Jim Reid), et Jim Reid a été nommé le 11 août 2016. Depuis, si on veut savoir ce que fait ce comité, il faut regarder sa page Web officielle. Son rôle n'est pas encore bien défini et fait l'objet de la plupart des discussions. En gros, il devrait intervenir uniquement lorsqu'une proposition de changement important est faite, pas pour la gestion quotidienne.

Téléchargez le RFC 8128

L'article seul

RFC 8095: Services Provided by IETF Transport Protocols and Congestion Control Mechanisms

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : G. Fairhurst (University of Aberdeen), B. Trammell, M. Kuehlewind (ETH Zurich)
Pour information
Réalisé dans le cadre du groupe de travail IETF taps
Première rédaction de cet article le 10 mars 2017

Les protocoles de transport (couche 4 dans le modèle en couches traditionnel), comme le fameux TCP, fournissent certains services aux applications situées au-dessus d'eux. Mais quels services exactement ? Qu'attend-on de la couche de transport ? Le but de ce RFC de synthèse est de lister tous les services possibles de la couche 4, et d'analyser ensuite tous les protocoles existants pour voir lesquels de ces services sont offerts. Ce document ne normalise donc pas un nouveau protocole, il classe et organise les protocoles existants. (L'idée est de pouvoir ensuite développer une interface abstraite permettant aux applications d'indiquer quels services elles attendent de la couche transport au lieu de devoir, comme c'est le cas actuellement, choisir un protocole donné. Une telle interface abstraite permettrait au système d'exploitation de choisir le protocole le plus adapté à chaque environnement.)

C'est d'autant plus important qu'il n'y a pas que TCP mais aussi des protocoles comme SCTP, UDP, DCCP, les moins connus FLUTE ou NORM, et même HTTP, qui est devenu une couche de transport de fait. Toute évolution ultérieure de l'architecture de l'Internet, des middleboxes, des API offertes par le système d'exploitation, implique une compréhension détaillée de ce que fait exactement la couche transport.

Pour TCP, tout le monde connait (ou croit connaitre) : il fournit un service de transport de données fiable (les données qui n'arrivent pas sont retransmises automatiquement, l'application n'a pas à s'en soucier, la non-modification est - insuffisamment - contrôlée via une somme de contrôle), et ordonné (les octets arrivent dans l'ordre d'envoi même si, dans le réseau sous-jacent, un datagramme en a doublé un autre). TCP ne fournit pas par contre de service de confidentialité, ce qui facilite le travail de la NSA ou de la DGSI. Tout le monde sait également qu'UDP ne fournit aucun des deux services de fiabilité et d'ordre : si l'application en a besoin, elle doit le faire elle-même (et il est donc logique que la plupart des applications utilisent TCP).

Parfois, le service de transport offert aux applications est lui-même bâti sur un autre service de transport. C'est la raison pour laquelle ce RFC présente des protocoles qui ne sont pas « officiellement » dans la couche 4 (mais, de toute façon, le modèle en couches n'a toujours été qu'une vague indication ; en faire une classification rigide n'a aucun intérêt, et a été une des raisons de l'échec du projet l'OSI). Un exemple est TLS. Une application qui s'en sert ne voit pas directement le TCP sous-jacent, elle confie ses données à TLS qui, à son tour, fait appel à TCP. Le service de transport vu par l'application offre ainsi les fonctions de TCP (remise fiable et ordonnée des données) plus celles de TLS (confidentialité, authentification et intégrité). Il faudrait être particulièrement pédant pour s'obstiner à classer TLS dans les applications comme on le voit parfois.

Le même phénomène se produit pour UDP : comme ce protocole n'offre quasiment aucun service par lui-même, on le complète souvent avec des services comme TFRC (RFC 5348) ou LEDBAT (RFC 6817) qui créent ainsi un nouveau protocole de transport au-dessus d'UDP.

La section 1 de notre RFC liste les services possibles d'une couche de transport :

  • Envoi des messages à un destinataire (unicast) ou à plusieurs (multicast ou anycast),
  • Unidirectionnel (ce qui est toujours le cas avec le multicast) ou bidirectionnel,
  • Nécessite un établissement de la connexion avant d'envoyer des données, ou pas,
  • Fiabilité de l'envoi (par un mécanisme d'accusé de réception et de réémission) ou bien fire and forget (notez que cette fiabilité peut être partielle, ce que permet par exemple SCTP),
  • Intégrité des données (par exemple via une somme de contrôle),
  • Ordre des données (avec certains protocoles de transport comme UDP, le maintien de l'ordre des octets n'est pas garanti, un paquet pouvant en doubler un autre),
  • Structuration des données (framing), certains protocoles découpent en effet les données en messages successifs (ce que ne fait pas TCP),
  • Gestion de la congestion,
  • Confidentialité,
  • Authentification (TLS fournit ces deux derniers services).

La section 3 du RFC est le gros morceau. Elle liste tous les protocoles de transport possibles (au moins ceux normalisés par l'IETF), en donnant à chaque fois une description générale du protocole, l'interface avec les applications, et enfin les services effectivement offerts par ce protocole.

À tout seigneur, tout honneur, commençons par l'archétype des protocoles de transport, TCP. Normalisé dans le RFC 793, très largement répandu (il est difficile d'imaginer une mise en œuvre d'IP qui ne soit pas accompagnée de TCP), utilisé quotidiennement par des milliards d'utilisateurs. Le RFC originel a connu pas mal de mises à jour et, aujourd'hui, apprendre TCP nécessite de lire beaucoup de RFC (le RFC 7414 en donne la liste). Ainsi, la notion de données urgentes, qui était dans le RFC originel, a été supprimée par le RFC 6093.

TCP multiplexe les connexions en utilisant les numéros de port, comme beaucoup de protocoles de transport. Une connexion est identifiée par un tuple {adresse IP source, port source, adresse IP destination, port destination}. Le port de destination identifie souvent le service utilisé (c'est moins vrai aujourd'hui, où la prolifération de middleboxes stupides oblige à tout faire passer sur les ports 80 et 443). TCP fournit un service de données non-structurées, un flot d'octets, mais, en interne, il découpe ces octets en segments, dont la taille est négociée au début (en général, TCP essaie de faire que cette taille soit la MTU du chemin, en utilisant les RFC 1191, RFC 1981 et de plus en plus le RFC 4821). Chaque octet envoyé a un numéro, le numéro de séquence, et c'est ainsi que TCP met en œuvre la fiabilité et l'ordre. (Contrairement à ce que croient certaines personnes, c'est bien l'octet qui a un numéro, pas le segment.) Autrefois, si deux segments non contigus étaient perdus, il fallait attendre la réémission du premier pour demander celle du second, mais les accusés de réception sélectifs du RFC 2018 ont changé cela.

Quant au contrôle de congestion de TCP, il est décrit en détail dans le RFC 5681. TCP réagit à la perte de paquets (ou bien à leur marquage avec l'ECN du RFC 3168) en réduisant la quantité de données envoyées.

Les données envoyées par l'application ne sont pas forcément transmises immédiatement au réseau. TCP peut attendre un peu pour remplir davantage ses segments (RFC 896). Comme certaines applications (par exemple celles qui sont fortement interactives comme SSH) n'aiment pas les délais que cela entraine, ce mécanisme est typiquement débrayable.

Enfin, pour préserver l'intégrité des données envoyées, TCP utilise une somme de contrôle (RFC 793, section 3.1, et RFC 1071). Elle ne protège pas contre toutes les modifications possibles et il est recommandé aux applications d'ajouter leur propre contrôle d'intégrité (par exemple, si on transfère un fichier, via un condensat du fichier).

Et l'interface avec les applications, cruciale, puisque le rôle de la couche transport est justement d'offrir des services aux applications ? Celle de TCP est décrite de manière relativement abstraite dans le RFC 793 (six commandes, Open, Close, Send, Receive, etc). Des points comme les options TCP n'y sont pas spécifiés. Le RFC 1122 est un peu plus détaillé, mentionnant par exemple l'accès aux messages ICMP qui peuvent indiquer une erreur TCP. Enfin, une interface concrète est celle des prises, normalisées par POSIX (pas de RFC à ce sujet). Vous créez une prise avec l'option SOCK_STREAM et hop, vous utilisez TCP et tous ses services.

Quels services, justement ? TCP fournit :

  • Établissement d'une connexion, et démultiplexage en utilisant les numéros de port,
  • Transport unicast (l'anycast est possible, si on accepte le risque qu'un changement de routes casse subitement une connexion),
  • Communication dans les deux sens,
  • Données envoyées sous forme d'un flot d'octets, sans séparation (pas de notion de message, c'est à l'application de le faire, si elle le souhaite, par exemple en indiquant la taille du message avant le message, comme le font EPP et DNS), c'est aussi cela qui permet l'accumulation de données avant envoi (algorithme de Nagle),
  • Transport fiable, les données arriveront toutes, et dans l'ordre,
  • Détection d'erreurs (mais pas très robuste),
  • Contrôle de la congestion, via les changements de taille de la fenêtre d'envoi (la fenêtre est l'ensemble des octets qui peuvent être envoyés avant qu'on ait reçu l'accusé de réception des données en cours), voir le RFC 5681.

Par contre, TCP ne fournit pas de confidentialité, et l'authentification se limite à une protection de l'adresse IP contre les attaquants situés hors du chemin (RFC 5961).

Après TCP, regardons le deuxième protocole de transport étudié, MPTCP (Multipath TCP, RFC 6824). C'est une extension de TCP qui permet d'exploiter le multi-homing. Pour échapper aux middleboxes intrusives, MPTCP fonctionne en créant plusieurs connexions TCP ordinaires depuis/vers toutes les adresses IP utilisées, et en multiplexant les données sur ces connexions (cela peut augmenter le débit, et cela augmente la résistance aux pannes, mais cela peut aussi poser des problèmes si les différents chemins ont des caractéristiques très différentes). La signalisation se fait par des options TCP.

L'interface de base est la même que celle de TCP, mais il existe des extensions (RFC 6897) pour tirer profit des particularités de MPTCP.

Les services sont les mêmes que ceux de TCP avec, en prime le multi-homing (il peut même y avoir des adresses IPv4 et IPv6 dans la même session MPTCP), et ses avantages notamment de résilience.

Après TCP, UDP est certainement le protocole de transport le plus connu. Il est notamment très utilisé par le DNS. Le RFC 8085 explique comment les applications peuvent l'utiliser au mieux. La section 3.3 de notre RFC lui est consacrée, pour décrire son interface et ses services.

Contrairement à TCP, UDP n'a pas la notion de connexion (on envoie directement les données, sans négociation préalable), UDP découpe les données en messages (voilà pourquoi les messages DNS en UDP ne sont pas précédés d'une longueur : UDP lui-même fait le découpage), n'a pas de contrôle de congestion, et ne garantit pas le bon acheminement. UDP dispose d'un contrôle d'intégrité, mais il est facultatif (quoique très recommandé) en IPv4, où on peut se contenter du contrôle d'intégrité d'IP. IPv6 n'ayant pas ce contrôle, UDP sur IPv6 doit activer son propre contrôle, sauf dans certains cas très précis (RFC 6936).

En l'absence de contrôle de congestion, l'application doit être prudente, veiller à ne pas surcharger le réseau, et ne pas s'étonner si l'émetteur envoie plus que ce que le récepteur peut traiter. D'une façon générale, il faut penser à lire le RFC 8085, qui explique en détail tout ce qu'une application doit faire si elle tourne sur UDP.

Il est d'ailleurs recommandé de bien se poser la question de l'utilité d'UDP, dans beaucoup de cas. Un certain nombre de développeurs se disent au début d'un projet « j'ai besoin de vitesse [sans qu'ils fassent bien la différence entre latence et capacité], je vais utiliser UDP ». Puis ils découvrent qu'ils ont besoin de contrôle de flux, d'ordre des données, de bonne réception des données, ils ajoutent à chaque fois des mécanismes ad hoc, spécifiques à leur application et, au bout du compte, ils ont souvent réinventé un truc aussi lourd que TCP, mais bien plus bogué. Attention donc à ne pas réinventer la roue pour rien.

L'interface d'UDP, maintenant. Le RFC 768 donne quelques indications de base, que le RFC 8085 complète. Bien qu'UDP n'ait pas le concept de connexion, il est fréquent que les API aient une opération connect() ou analogue. Mais il ne faut pas la confondre avec l'opération du même nom sur TCP : ce connect() UDP est purement local, associant la structure de données locale à une machine distante (c'est ainsi que cela se passe avec les prises Berkeley).

Et les services d'UDP ? La liste est évidemment bien plus courte que pour TCP. Elle comprend :

  • Transport des données, unicast, multicast , anycast et broadcast (c'est le seul point où UDP en fournit davantage que TCP),
  • Démultiplexage en utilisant les numéros de port,
  • Unidirectionnel (ce qui est toujours le cas avec le multicast) ou bidirectionnel,
  • Données structurées en messages,
  • Aucune garantie, ou signalement, des pertes de message,
  • Aucune garantie sur l'ordre de délivrance des messages.

Nettement moins connu qu'UDP est UDP-Lite, normalisé dans le RFC 3828. C'est une version très légèrement modifiée d'UDP, où la seule différence est que les données corrompues (détectées par la somme de contrôle) sont quand même données à l'application réceptrice, au lieu d'être jetées comme avec UDP. Cela peut être utile pour certains applications, notamment dans les domaines audio et vidéo.

Avec UDP-Lite, le champ Longueur de l'en-tête UDP change de sémantique : il n'indique plus la longueur totale des données mais la longueur de la partie qui est effectivement couverte par la somme de contrôle. Typiquement, on ne couvre que l'en-tête applicatif. Le reste est... laissé à la bienveillance des dieux (ou des démons). Pour tout le reste, voyez la section sur UDP.

Notez qu'il n'existe pas d'API spécifique pour UDP-Lite. Si quelqu'un parmi mes lecteurs a des exemples de code bien clairs...

Bien plus original est SCTP (RFC 4960). C'est un protocole à connexion et garantie d'acheminement et d'ordre des données, comme TCP. Mais il s'en distingue par sa gestion du multi-homing. Avec SCTP, une connexion peut utiliser plusieurs adresses IP source et destination, et passer de l'une à l'autre pendant la session, assurant ainsi une bonne résistance aux pannes. Plus drôle, cet ensemble d'adresses peut mêler des adresses IPv4 et IPv6.

Notez aussi qu'une connexion SCTP (on dit une association) comporte plusieurs flux de données, afin de minimiser le problème connu sous le nom de head of line blocking (un paquet perdu empêche la délivrance de toutes les données qui suivent tant qu'il n'a pas été réémis).

SCTP avait surtout été conçu pour la signalisation dans les réseaux téléphoniques. Mais on le trouve dans d'autres cas, comme ForCES (cf. RFC 5811) ou comme la signalisation WebRTC.

Contrairement à TCP, SCTP utilise une quadruple poignée de mains pour établir la connexion, ce qui permet de ne négocier les options qu'une fois certain de l'identité du partenaire (les techniques anti-DoS de TCP sont incompatible avec l'utilisation des options, cf. RFC 4987, section 3.6). La somme de contrôle fait 32 bits (au lieu des 16 bits de TCP et UDP) et est donc normalement plus robuste.

SCTP est très extensible et plusieurs extensions ont déjà été définies comme l'ajout ou le retrait d'adresses IP pendant l'association (RFC 5061), ou bien la possibilité de n'accepter qu'une fiabilité partielle (RFC 3758). Pour la sécurité, on peut faire tourner TLS sur SCTP (RFC 3436) au prix de la perte de quelques fonctions, ou bien utiliser DTLS (RFC 6083), qui préserve quasiment toutes les fonctions de SCTP.

Victime fréquente des middleboxes stupides qui ne connaissent qu'UDP et TCP, SCTP peut tourner sur UDP (RFC 6951), au lieu de directement reposer sur IP, afin de réussir à passer ces middleboxes.

Contrairement à des protocoles de transport plus anciens, SCTP a une interface bien spécifiée. Le RFC 4960 définit l'interface abstraite, et une extension aux prises Berkeley, spécifiée dans le RFC 6458, lui donne une forme concrète. Cette API prévoit également certaines extensions, comme celle des reconfigurations dynamiques d'adresses du RFC 5061.

Les services fournis par SCTP sont très proches de ceux fournis par TCP, avec deux ajouts (la gestion du multi-homing et le multi-flux), et un changement (données structurées en messages, au lieu d'être un flot d'octets continu comme TCP).

Un autre protocole de transport peu connu, et ne fournissant pas, lui, de fiabilité de l'envoi des données, est DCCP (RFC 4340). DCCP est une sorte d'UDP amélioré, qui peut fournir des services supplémentaires à ceux d'UDP, tout en restant plus léger que TCP (la description du besoin figure dans le RFC 4336). DCCP est bien adapté aux applications multimédia ou aux jeux en ligne, où une faible latence est cruciale, mais où peut aimer avoir des services en plus. Sans DCCP, chaque application qui veut de l'« UDP amélioré » devrait tout réinventer (et ferait sans doute des erreurs).

DCCP a des connexions, comme TCP, qu'on établit avant de communiquer et qu'on ferme à la fin. Il offre une grande souplesse dans le choix des services fournis, choix qui peuvent être unilatéraux (seulement l'envoyeur, ou bien seulement le récepteur) ou négociés lors de l'ouverture de la connexion. Le paquet d'ouverture de connexion indique l'application souhaitée (RFC 5595), ce qui peut être une information utile aux équipements intermédiaires. S'il faut faire passer DCCP à travers des middleboxes ignorantes, qui n'acceptent qu'UDP et TCP, on peut, comme avec SCTP, encapsuler dans UDP (RFC 6773).

L'interface avec DCCP permet d'ouvrir, de fermer et de gérer une connexion. Il n'y a pas d'API standard. Les services fournis sont :

  • Transport des données, uniquement unicast,
  • Protocole à connexion, et démultiplexage fondé sur les numéros de port,
  • Structuration des données en messages,
  • Les messages peuvent être perdus (mais, contrairement à UDP, l'application est informée des pertes), et ils peuvent être transmis dans le désordre,
  • Contrôle de la congestion (le gros avantage par rapport à UDP), et avec certains choix (optimiser la latence ou au contraire la gigue, par exemple) laissés à l'application.

Autre exemple de protocole de transport, même s'ils ne sont en général pas décrits comme tels, TLS (RFC 5246) et son copain DTLS (RFC 6347). Si on est un fanatique du modèle en couches, on ne met pas ces protocoles de sécurité en couche 4 mais, selon l'humeur, en couche 5 ou en couche 6. Mais si on est moins fanatique, on reconnait que, du point de vue de l'application, ce sont bien des protocoles de transport : c'est à eux que l'application confie ses données, comptant sur les services qu'ils promettent.

TLS tourne sur TCP et DTLS sur UDP. Du point de vue de l'application, TLS fournit les services de base de TCP (transport fiable d'un flot d'octets) et DTLS ceux d'UDP (envoi de messages qui arriveront peut-être). Mais ils ajoutent à ces services de base leurs services de sécurité :

Le RFC rappelle qu'il est important de se souvenir que TLS ne spécifie pas un mécanisme d'authentification unique, ni même qu'il doit y avoir authentification. On peut n'authentifier que le serveur (c'est actuellement l'usage le plus courant), le client et le serveur, ou bien aucun des deux. La méthode la plus courante pour authentifier est le certificat PKIX (X.509), appelé parfois par une double erreur « certificat SSL ».

DTLS ajoute également au service de base quelques trucs qui n'existent pas dans UDP, comme une aide pour la recherche de PMTU ou un mécanisme de cookie contre certaines attaques.

Il n'y a pas d'API standard de TLS. Si on a écrit une application avec l'API d'OpenSSL, il faudra refaire les appels TLS si on passe à WolfSSL ou GnuTLS. C'est d'autant plus embêtant que les programmeurs d'application ne sont pas forcément des experts en cryptographie et qu'une API mal conçue peut les entrainer dans des erreurs qui auront des conséquences pour la sécurité (l'article « The most dangerous code in the world: validating SSL certificates in non-browser software » en donne plusieurs exemples).

Passons maintenant à RTP (RFC 3550). Ce protocole est surtout utilisé pour les applications multimédia, où on accepte certaines pertes de paquet, et où le format permet de récupérer après cette perte. Comme TLS, RTP fonctionne au-dessus du « vrai » protocole de transport, et peut exploiter ses services (comme la protection de l'intégrité d'une partie du contenu, que fournissent DCCP et UDP-Lite).

RTP comprend en fait deux protocoles, RTP lui-même pour les données et RTCP pour le contrôle. Par exemple, c'est via RTCP qu'un émetteur apprend que le récepteur ne reçoit pas vite et donc qu'il faudrait, par exemple, diminuer la qualité de la vidéo.

RTP n'a pas d'interface standardisée offerte aux programmeurs. Il faut dire que RTP est souvent mis en œuvre, non pas dans un noyau mais directement dans l'application (comme avec libortp sur Unix). Ces mises en œuvre sont donc en général optimisées pour une utilisation particulière, au lieu d'être généralistes comme c'est le cas avec les implémentations de TCP ou UDP.

Autre cas d'un protocole de transport qui fonctionne au-dessus d'un autre protocole de transport, HTTP (RFC 7230 et suivants). Il n'était normalement pas conçu pour cela mais, dans l'Internet d'aujourd'hui, où il est rare d'avoir un accès neutre, où les ports autres que 80 et 443 sont souvent bloqués, et où hôtels, aéroports et écoles prétendent fournir un « accès Internet » qui n'est en fait qu'un accès HTTP, bien des applications qui n'ont rien à voir avec le Web en viennent à utiliser HTTP comme protocole de transport. (Même si le RFC 3205 n'encourage pas vraiment cette pratique puisque HTTP peut ne pas être adapté à tout. Mais, souvent, on n'a pas le choix.)

Outre cette nécessité de contourner blocages et limitations, l'utilisation de HTTP comme transport a quelques avantages : protocole bien connu, disposant d'un grand nombre de mises en œuvre, que ce soit pour les clients ou pour les serveurs, et des mécanismes de sécurité existants (RFC 2617, RFC 2817…). L'un des grands succès de HTTP est le style REST : de nombreuses applications sont conçues selon ce style.

Les applications qui utilisent HTTP peuvent se servir des méthodes existantes (GET, PUT, etc) ou bien en créer de nouvelles (qui risquent de moins bien passer partout).

Je ne vais pas refaire ici la description de HTTP que contient le RFC (suivant le même plan que pour les autres protocoles de transport), je suppose que vous connaissez déjà HTTP. Notez quand même quelques points parfois oubliés : HTTP a un mécanisme de négociation du contenu, qui permet, par exemple, de choisir le format lorsque la ressource existe en plusieurs formats, HTTP a des connexions persistentes donc on n'est pas obligé de se taper un établissement de connexion TCP par requête, et HTTP a des mécanismes de sécurité bien établis, à commencer par HTTPS.

Il y a plein de bibliothèques qui permettent de faire de l'HTTP facilement (libcurl et neon en C, Requests en Python, etc). Chacune a une API différente. Le W3C a normalisé une API nommée XMLHttpRequest, très utilisée par les programmeurs JavaScript.

Les services que fournit HTTP quand on l'utilise comme protocole de transport sont :

  • Transport unicast, bi-directionnel, fiable (grâce à TCP en dessous), et avec contrôle de congestion (idem),
  • Négociation du format, possibilité de ne transférer qu'une partie d'une ressource,
  • Authentification et confidentialité si on utilise HTTPS.

Beaucoup moins connus que les protocoles précédents sont deux des derniers de notre liste, FLUTE et NORM.

FLUTE (File Delivery over Unidirectional Transport/ Asynchronous Layered Coding Reliable Multicast) est normalisé dans le RFC 6726. Il est conçu pour un usage très spécifique, la distribution de fichiers à des groupes multicast de grande taille, où on ne peut pas demander à chaque récepteur d'accuser réception. Il est surtout utilisé dans le monde de la téléphonie mobile (par exemple dans la spécification 3GPP TS 26.346).

FLUTE fonctionne sur UDP, et le protocole ALC du RFC 5775. Il est souvent utilisé sur des réseaux avec une capacité garantie, et où on peut donc relativiser les problèmes de congestion. Il n'y a pas d'interface de programmation spécifiée.

Les services de FLUTE sont donc :

  • Transport de fichiers (que FLUTE appelle « objets ») plutôt que d'octets,
  • Fiable (heureusement, pour des fichiers).

Et NORM (NACK-Oriented Reliable Multicast ? Normalisé dans le RFC 5740, il rend à peu près les mêmes services que FLUTE (distribution massive de fichiers). À noter qu'il en existe une mise en œuvre en logiciel libre.

Reste un cas amusant, ICMP. Bien sûr, ICMP n'est pas du tout conçu pour être un protocole de transport, c'est le protocole de signalisation d'IP (RFC 792 pour ICMP sur IPv4 et RFC 4443 pour ICMP sur IPv6). Mais, bon, comme il est situé au-dessus de la couche 3, on peut le voir comme un protocole de transport.

Donc, ICMP est sans connexion, sans fiabilité, et unidirectionnel. Évidemment pas de contrôle de congestion. Pas vraiment d'interface standard, les messages ICMP ne sont signalés qu'indirectement aux applications (dans certains cas, une application peut demander à recevoir les messages ICMP). On ne peut pas tellement s'en servir comme protocole de transport, bien que des programmes comme ptunnel s'en servent presque ainsi.

Après cette longue section 3 qui faisait le tour de tous les protocoles de transport ou assimilés, la section 4 de notre RFC revient sur la question cruciale de la congestion. Sans contrôle de congestion, si chacun émettait comme ça lui chante, l'Internet s'écroulerait vite sous la charge. C'est donc une des tâches essentielles d'un protocole de transport que de fournir ce contrôle de congestion. Pour ceux qui ne le font pas, l'application doit le faire (et c'est très difficile à faire correctement).

À noter que la plupart des protocoles de transport tendent à ce que chaque flot de données utilise autant de capacité disponible que les autres flots. Au contraire, il existe des protocoles « décroissants » comme LEDBAT (RFC 6817) qui cèdent la place aux autres et n'utilise la capacité que lorsque personne n'est en concurrence avec eux.

La section 5 de notre RFC revient sur la notion de fonctions fournies par le protocole de transport, et classe sur un autre axe que la section 3. La section 3 était organisée par protocole et, pour chaque protocole, indiquait quelles étaient ses fonctions. La section 5, au contraire, est organisée par fonction et indique, pour chaque fonction, les valeurs qu'elle peut prendre, et les protocoles qui correspondent. Première catégorie de fonctions, celle du contrôle. Ainsi, une des fonctions de base d'un protocole de transport est l'adressage, celui-ci peut être unicast (TCP, UDP, SCTP, TLS, HTTP), multicast (UDP encore, FLUTE, NORM), broadcast (UDP toujours), anycast (UDP, quoique TCP puisse l'utiliser si on accepte le risque de connexions coupées lorsque le routage change).

Autre fonction, la façon dont se fait l'association entre les deux machines, et elle peut être avec connexion (TCP, SCTP, TLS) ou sans connexion (UDP). La gestion du multi-homing peut être présente (MPTCP, SCTP) ou pas. La signalisation peut être faite avec ICMP ou bien dans le protocole d'application (RTP).

Seconde catégorie de fonctions, la délivrance de données. Première fonction dans cette catégorie, la fiabilité, qui peut être complète (TCP, SCTP, TLS), partielle (RTP, FLUTE, NORM) ou inexistante (UDP, DCCP). Deuxième fonction, la détection d'erreurs, par une somme de contrôle qui couvre toutes les données (TCP, UDP, SCTP, TLS), une partie (UDP-Lite), et qui peut même être optionnelle (UDP en IPv4). Troisième fonction de délivrance, l'ordre des données, qui peut être maintenu (TCP, SCTP, TLS, HTTP, RTP) ou pas (UDP, DCCP, DTLS). Quatrième fonction, le découpage des données : flot sans découpage (TCP, TLS) ou découpage en messages (UDP, DTLS).

Troisième catégorie de fonctions, celles liées au contrôle de la transmission et notamment de la lutte contre la congestion.

Enfin, quatrième et dernière catégorie de fonctions, celles liées à la sécurité : authentification (TLS, DTLS) et confidentialité (les mêmes) notamment.

Voilà, armé de ce RFC, si vous êtes développeurs d'un nouveau protocole applicatif sur Internet, vous pouvez choisir votre protocole de transport sans vous tromper.

Téléchargez le RFC 8095

L'article seul

RFC 8086: GRE-in-UDP Encapsulation

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : L. Yong (Huawei Technologies), E. Crabbe (Oracle), X. Xu (Huawei Technologies), T. Herbert (Facebook)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF tsvwg
Première rédaction de cet article le 9 mars 2017

Le protocole de tunnel GRE, normalisé dans les RFC 2784 et RFC 7676, tourne normalement directement sur IP (numéro de protocole 47, TCP étant 6 et UDP 17). Cela présente quelques inconvénients, notamment la difficulté à passer certaines middleboxes, et ce nouveau RFC propose donc une encapsulation de GRE dans UDP, et non plus directement dans IP. Un des autres avantages de cette encapsulation est que le port source UDP peut être utilisé comme une source d'entropie supplémentaire : sa vérification permet d'améliorer la (faible) sécurité de GRE. GRE sur UDP permet aussi l'utilisation de DTLS si on veut chiffrer (ce que ne fait pas GRE classiquement).

Un autre avantage est que l'encapsulation dans UDP peut améliorer les performances, dans le cas où il y a des répartiteurs de charge ECMP : ils pourront alors faire passer tous les paquets d'un même tunnel GRE par le même chemin, puisqu'ils prennent leurs décisions sur la base du tuple {protocole, adresse IP source, adresse IP destination, port source, port destination}.

Vu du réseau, un tunnel GRE sur UDP sera juste du trafic UDP normal. Attention, toutefois, le trafic UDP sur l'Internet public doit normalement obéir à certaines règles, notamment de contrôle de la congestion (ces règles figurent dans le RFC 8085). Avec TCP, c'est le protocole de transport qui s'en charge, avec UDP, c'est à l'application de le faire. Si on transporte du trafic quelconque, pas spécialement raisonnable, dans un tunnel GRE sur UDP, on viole les règles du RFC 8085. Il faut donc s'assurer que le trafic dans le tunnel a des mécanismes de contrôle de la congestion, ou bien réserver GRE sur UDP à des réseaux fermés, où on prend les risques qu'on veut. (Voir aussi la section 8 de notre RFC.)

Donc, on peut se servir de GRE sur UDP au-dessus d'IPv4 ou d'IPv6 (section 2 du RFC). La somme de contrôle UDP est très recommandée (elle est obligatoire en IPv6). On doit vérifier que le trafic transporté fera attention au contrôle de congestion. Le port source UDP doit être dans la plage des ports éphémères (de 49 152 à 65 535, voir aussi la section 3.2.1). Utiliser un port par flot encapsulé facilite la tâche des équipements comme les répartiteurs de trafic. Mais on peut aussi n'utiliser qu'un seul port pour tout ce qui passe dans le tunnel et, dans ce cas, il faut le choisir de manière imprévisible, pour des raisons de sécurité (RFC 6056). Et en IPv6, merci de penser à utiliser le flow label (RFC 6438).

Le port de destination, lui, est par défaut 4754 pour de l'UDP ordinaire et 4755 pour du DTLS.

Ce protocole GRE sur UDP a eu une histoire longue et compliquée, pris dans des efforts pour fournir des mécanismes génériques d'encapsulation dans UDP (projet GUE), efforts qui n'ont guère débouché (cf. le RFC 7510 pour un autre exemple que GRE).

Voilà, après ces grands principes, le format exact (section 3). Au-dessus de l'en-tête IP (v4 ou v6), on met un en-tête UDP (RFC 768) et un en-tête GRE (RFC 2784).

La section 5 du RFC couvre le cas de DTLS (RFC 6347), qui a l'avantage de donner à GRE les moyens de chiffrer le trafic, sans modifier GRE lui-même.

Évidemment, dans l'Internet réellement existant, le problème, ce sont les middleboxes (section 7 du RFC). C'est d'ailleurs parfois uniquement à cause d'elles qu'il faut utiliser GRE sur UDP et pas GRE tout court, car certaines se permettent de bloquer les protocoles qu'elles ne connaissent pas (typiquement, tout sauf UDP et TCP).

Même en mettant GRE dans UDP, tous les problèmes ne sont pas résolus. Le trafic GRE est unidirectionnel (il y a en fait deux tunnels différents, chacun à sens unique). Il n'y est pas censé avoir des réponses au port source du trafic. Mais certaines middleboxes vont insister pour que ce soit le cas. Une solution possible, pour ces middleboxes pénibles, est de n'utiliser qu'un seul port source.

Il existe des mises en œuvre de ce RFC pour Linux et BSD. Les tests suivants ont été faits sur des machines Linux, noyaux 4.4 et 4.8. ip tunnel ne fournit pas de choix pour « GRE sur UDP ». Il faut passer par le système FOU (Foo-over-UDP, cf. cet article de LWN), qui a l'avantage d'être plus générique :

# modprobe fou      
# lsmod|grep fou
fou                    20480  0
ip_tunnel              28672  1 fou
ip6_udp_tunnel         16384  1 fou
udp_tunnel             16384  1 fou

La machine qui va recevoir les paquets doit configurer FOU pour indiquer que les paquets à destination de tel port UDP sont en fait du GRE :

# ip fou add port 4754 ipproto 47

(47 = GRE) La machine émettrice, elle, doit créer une interface GRE encapsulée grâce à FOU :

#  ip link add name tun1 type gre \
          remote $REMOTE local $LOCAL ttl 225 \
          encap fou encap-sport auto encap-dport 4754
# ip link   set tun1 up

Et il faut évidemment configurer une route passant par cette interface tun1, ici pour le préfixe :

# ip route add dev tun1

Avec cette configuration, lorsque la machine émettrice pingue, les paquets arrivent bien sur la machine réceptrice :

12:10:40.138768 IP (tos 0x0, ttl 215, id 10633, offset 0, flags [DF], proto UDP (17), length 116) > [no cksum] UDP, length 88

On peut les examiner plus en détail avec Wireshark :

User Datagram Protocol, Src Port: 1121 (1121), Dst Port: 4754 (4754)
    Source Port: 1121
    Destination Port: 4754
    Length: 96
    Checksum: 0x0000 (none)
        [Good Checksum: False]
        [Bad Checksum: False]
    [Stream index: 0]
Data (88 bytes)

0000  00 00 08 00 45 00 00 54 3e 99 40 00 40 01 ef 6f   ....E..T>.@.@..o

Wireshark ne connait apparemment pas le GRE sur UDP. Mais, dans les données, on reconnait bien l'en-tête GRE (les quatre premiers octets où presque tous les bits sont à zéro, le bit C étant nul, les quatre octets suivants optionnels ne sont pas inclus, le 0x800 désigne IPv4, cf. RFC 2784), et on voit un paquet IPv4 ensuite. Pour que ce paquet soit correctement traité par la machine réceptrice, il faut le transmettre à GRE. Comme ce dernier n'a pas de mécanisme permettant de mettre plusieurs tunnels sur une même machine (l'en-tête GRE n'inclut pas d'identificateurs), il faut activer l'unique interface GRE :

# ip link set gre0 up   

On voit bien alors notre ping qui arrive :

# tcpdump -vv -n -i gre0                                         
tcpdump: listening on gre0, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
14:02:06.922367 IP (tos 0x0, ttl 64, id 47453, offset 0, flags [DF], proto ICMP (1), length 84) > ICMP echo request, id 13947, seq 17, length 64

Voilà, je vous laisse faire la configuration en sens inverse.

Si vous voulez en savoir plus sur la mise en œuvre de FOU, voyez cet excellent exposé d'un des auteurs, Tom Herbert, cet article du même, et enfin sa vidéo.

Téléchargez le RFC 8086

L'article seul

RFC 8085: UDP Usage Guidelines

Date de publication du RFC : Mars 2017
Auteur(s) du RFC : L. Eggert (NetApp), G. Fairhurst (University of Aberdeen), G. Shepherd (Cisco Systems)
Réalisé dans le cadre du groupe de travail IETF tsvwg
Première rédaction de cet article le 9 mars 2017

La grande majorité des applications Internet tourne sur le protocole de transport TCP. Mais son concurrent UDP, normalisé dans le RFC 768, prend de l'importance avec le multimédia et les jeux en ligne pour lesquels il est souvent bien adapté. Contrairement à TCP, UDP ne fournit aucun mécanisme de contrôle de la congestion. C'est donc aux applications de fournir ce contrôle, suivant les règles expliquées par ce RFC. (Qui parle surtout de congestion mais aussi d'autres sujets importants pour ceux qui utilisent UDP, comme la taille des messages ou comme les sommes de contrôle.) Il remplace le RFC 5405.

UDP est apprécié pour certaines applications car il est simple et léger et le fait qu'il ne garantisse pas l'acheminement de la totalité des paquets n'est pas forcément un problème dans les applications multimédia : si on perd quelques secondes d'une communication téléphonique RTP, il vaut mieux passer à la suite que de perdre du temps à la retransmettre comme le ferait TCP. Mais UDP ne fournit pas non plus de contrôle de la congestion. Une application UDP enthousiaste peut envoyer des paquets au débit maximum permis, saturant tous les liens situés en aval. (Il ne faut pas juste tenir compte de la capacité du lien auquel on est connecté, mais de celle du chemin complet. L'exemple du RFC, avec un chemin à seulement 56 kb/s, que cinq paquets UDP de 1 500 octets par seconde saturent, n'est pas invraisemblable.) Protéger le réseau de la congestion est pourtant nécessaire (RFC 2914 et RFC 7567), à la fois pour assurer que le réseau continue à être utilisable et également pour assurer une certaine équité entre les différents flux de données, pour éviter qu'une seule application gourmande ne monopolise le réseau pour elle. (Ceci concerne l'Internet public. Si on est dans un environnement fermé, utilisant TCP/IP mais où la capacité réseau, et son usage, sont contrôlés, le problème est évidemment différent. Voir notamment la section 3.6.)

UDP ne faisant pas ce contrôle de congestion, il faut bien que l'application le fasse et, pour cela, qu'elle mette en œuvre les conseils de ce RFC. (Notre RFC contient également des conseils pour d'autres aspects de l'utilisation d'UDP que le contrôle de congestion : mais c'est le plus important.)

Le gros du RFC est dans la section 3 qui détaille ces conseils (la section 7 contient un excellent résumé sous forme d'un tableau des conseils à suivre). Le premier est qu'il vaut peut-être mieux ne pas utiliser UDP. Beaucoup de développeurs d'applications pensent à UDP en premier parce qu'il est simple et facile à comprendre et qu'il est « plus rapide que TCP ». Mais, rapidement, ces développeurs se rendent compte qu'ils ont besoin de telle fonction de TCP, puis de telle autre, ils les mettent en œuvre dans leur application et arrivent à une sorte de TCP en moins bien, d'avantage bogué et pas plus rapide. Notre RFC conseille donc d'abord de penser aux autres protocoles de transport comme TCP (RFC 793), DCCP (RFC 4340) ou SCTP (RFC 4960). Ces protocoles sont d'autant plus intéressants qu'ils ont souvent fait l'objet de réglages soigneux depuis de nombreuses années et qu'il est donc difficile à un nouveau programme de faire mieux. D'autant plus qu'il existe souvent des réglages spécifiques pour les adapter à un usage donné. Par exemple, on peut dire à TCP de donner la priorité à la latence (paramètre TCP_NODELAY de setsockopt) ou bien au débit.

Si on ne suit pas ces sages conseils, et qu'on tient à se servir d'UDP, que doit-on faire pour l'utiliser intelligemment ? La section 3.1 couvre le gros morceau, le contrôle de congestion. Celui-ci doit être pris en compte dès la conception de l'application. Si cette dernière fait de gros transferts de données (section 3.1.2, c'est le cas de RTP, RFC 3550), elle doit mettre en œuvre TFRC, tel que spécifié dans le RFC 5348, donc faire à peu près le même travail que TCP. Et ce mécanisme doit être activé par défaut.

Si l'application transmet peu de données (section 3.1.3), elle doit quand même faire attention et le RFC demande pas plus d'un datagramme par RTT, où le RTT est un cycle aller-retour avec la machine distante (si le calcul n'est pas possible, le RFC demande une durée de trois secondes). L'application doit également détecter les pertes de paquet pour ralentir son rythme si ces pertes - signe de congestion - sont trop fréquentes.

Si l'application est bi-directionnelle (le cas de loin le plus fréquent), le contrôle de la congestion doit se faire indépendamment dans les deux directions.

Notez que se retenir d'envoyer des paquets n'est pas le seul moyen pour une application d'éviter la congestion. Elle peut aussi (si l'API utilisée le permet) se servir d'ECN (RFC 3168) pour transmettre l'information qui permettra de réguler le trafic.

Enfin, le RFC demande (section 3.1.10) un mécanisme de « disjoncteur » (circuit breaker, cf. RFC 8084 ou bien RFC 8083 pour l'exemple spécifique de RTP). C'est un mécanisme de dernier recours pour couper la communication en cas de risque d'effondrement du réseau.

Le cas où l'application est un tunnel au-dessus d'UDP est également couvert (section 3.1.11). C'est par exemple le cas du protocole GRE quand il tourne sur UDP (RFC 8086).

En suivant toutes ces règles, l'application gère proprement la congestion. Et le reste ? La section 3.2 fournit des pistes sur la gestion de la taille des paquets. La charge utile d'un paquet UDP peut théoriquement faire 65 507 octets en IPv4 et 65 527 en IPv6. Mais c'est théorique. En pratique, la fragmentation marche mal sur l'Internet, et notre RFC conseille de rester en dessous de la MTU, et d'utiliser la découverte de la MTU du chemin spécifiée dans des RFC comme le RFC 4821. (Aujourd'hui, la principale application qui envoie des paquets UDP plus gros que la MTU, et doit donc se battre avec la fragmentation, est le DNS ; voir par exemple l'étude de Geoff Huston sur les comportements très variés des serveurs de la racine.)

La section 3.3 explique la question de la fiabilité : par défaut, UDP ne retransmet pas les paquets perdus. Si c'est nécessaire, c'est l'application qui doit le faire. Elle doit aussi gérer l'eventuelle duplication des paquets (qu'UDP n'empêche pas). Le RFC note que les retards des paquets peuvent être très importants (jusqu'à deux minutes, normalise le RFC, ce qui me semble très exagéré pour l'Internet) et que l'application doit donc gérer le cas où un paquet arrive alors qu'elle croyait la session finie depuis longtemps.

La section 3.4 précise l'utilisation des sommes de contrôle (facultatives pour UDP sur IPv4 mais qui devraient être utilisées systématiquement). Si une somme de contrôle pour tout le paquet semble excessive, et qu'on veut protéger uniquement les en-têtes de l'application, une bonne alternative est UDP-Lite (RFC 3828), décrit dans la section 3.4.2. (Il y a aussi des exceptions à la règle « somme de contrôle obligatoire en IPv6 » dans le cas de tunnels.)

Beaucoup de parcours sur l'Internet sont encombrés de « middleboxes », ces engins intermédiaires qui assurent diverses fonctions (NAT, coupe-feu, etc) et qui sont souvent de médiocre qualité logicielle, bricolages programmés par un inconnu et jamais testés. La section 3.5 spécifie les règles que devraient suivre les applications UDP pour passer au travers sans trop de heurts. Notamment, beaucoup de ces « middleboxes » doivent maintenir un état par flux qui les traverse. En TCP, il est relativement facile de détecter le début et la fin d'un flux en observant les paquets d'établissement (SYN) et de destruction (FIN) de la connexion. En UDP, ces paquets n'ont pas d'équivalent et la détection d'un flux repose en général sur des heuristiques. L'engin peut donc se tromper et mettre fin à un flux qui n'était en fait pas terminé. Si le DNS s'en tire en général (c'est un simple protocole requête-réponse, avec la lupart du temps moins d'une seconde entre l'une et l'autre), d'autres protocoles basés sur UDP pourraient avoir de mauvaises surprises. Ces protocoles doivent donc se préparer à de soudaines interruptions de la communication, si le timeout d'un engin intermédiaire a expiré alors qu'il y avait encore des paquets à envoyer. (La solution des keepalives est déconseillée par le RFC car elle consomme de la capacité du réseau et ne dispense pas de gérer les coupures, qui se produiront de toute façon.)

La section 5 fera le bonheur des programmeurs qui y trouveront des conseils pour mettre en œuvre les principes de ce RFC, via l'API des prises (sockets, RFC 3493). Elle est largement documentée mais en général plutôt pour TCP que pour UDP, d'où l'intérêt du résumé qu'offre ce RFC, qui ne dispense évidemment pas de lire le Stevens. Par exemple, en l'absence de mécanisme de TIME_WAIT (la prise reste à attendre d'éventuels paquets retardés, même après sa fermeture par l'application), une application UDP peut ouvrir une prise... et recevoir immédiatement des paquets qu'elle n'avait pas prévus, qui viennent d'une exécution précédente.

Le RFC détaille également la bonne stratégie à utiliser pour les ports. Il existe un registre des noms et numéros de ports (RFC 6335), et le RFC 7605 explique comment utiliser les ports. Notre RFC conseille notamment de vérifier les ports des paquets reçus, entre autre pour se protéger de certaines attaques, où l'attaquant, qui ne peut pas observer le trafic et doit injecter des paquets aveuglément, ne connait pas les ports utilisés (en tout cas pas les deux). L'application devrait utiliser un port imprévisible, comme le fait TCP (RFC 6056). Pour avoir suffisamment d'entropie pour les répartiteurs de charge, le RFC rappelle qu'en IPv6, on peut utiliser le champ flow label (RFC 6437 et RFC 6438).

Le protocole ICMP fournit une aide utile, que les applications UDP peuvent utiliser (section 5.2). Mais attention, certains messages ICMP peuvent refléter des erreurs temporaires (absence de route, par exemple) et ne devraient pas entraîner de mesures trop drastiques. Autre piège, il est trivial d'envoyer des faux paquets ICMP. Une application doit donc essayer de déterminer, en examinant le contenu du message ICMP, s'il est authentique. Cela nécessite de garder un état des communications en cours, ce que TCP fait automatiquement mais qui, pour UDP, doit être géré par l'application. Enfin, il faut se rappeler que pas mal de middleboxes filtrent stupidement l'ICMP et l'application doit donc être prête à se débrouiller sans ces messages.

Après tous ces conseils, la section 6 est dédiée aux questions de sécurité. Comme TCP ou SCTP, UDP ne fournit en soi aucun mécanisme d'intégrité des données ou de confidentialité. Pire, il ne fournit même pas d'authentification de l'adresse IP source (authentification fournie, avec TCP, par le fait que, pour établir la connexion, il faut recevoir les réponses de l'autre). Cela permet, par exemple, les injections de faux trafic (contre lesquelles il est recommandé d'utiliser des ports source imprévisibles, comme le fait le DNS), ou bien les attaques par amplification.

L'application doit-elle mettre en œvre la sécurité seule ? Le RFC conseille plutôt de s'appuyer sur des protocoles existants comme IPsec (RFC 4301, dont notre RFC note qu'il est très peu déployé) ou DTLS (RFC 6347). En effet, encore plus que les protocoles de gestion de la congestion, ceux en charge de la sécurité sont très complexes et il est facile de se tromper. Il vaut donc mieux s'appuyer sur un système existant plutôt que d'avoir l'hubris et de croire qu'on peut faire mieux que ces protocoles ciselés depuis des années.

Pour authentifier, il existe non seulement IPsec et DTLS mais également d'autres mécanismes dans des cas particuliers. Par exemple, si les deux machines doivent être sur le même lien (un cas assez courant), on peut utiliser GTSM (RFC 3682) pour s'en assurer.

Enfin, notre RFC se termine (section 7) par un tableau qui synthétise les recommandations, indiquant à chaque fois la section du RFC où cette recommandation est développée. Développeu·r·se d'applications utilisant UDP, si tu ne lis qu'une seule section du RFC, cela doit être celle-ci !

Quels changements depuis le RFC précédent, le RFC 5405 ? Le fond des recommandations reste le même, la principale addition est celle de nombreuses recommandations spécifiques au multicast (dont je n'ai pas parlé ici) mais aussi à l'anycast, aux disjoncteurs, et aux tunnels. Il y a également l'introduction d'une différence entre l'Internet public (où il se faut se comporter en bon citoyen) et des réseaux privés et fermés utilisant les mêmes protocoles, mais où on a droit à des pratiques qui seraient jugées anti-sociales sur l'Internet public (comme d'envoyer des paquets sans tenir compte de la congestion). Ce RFC est donc bien plus long que son prédécesseur.

Téléchargez le RFC 8085

L'article seul

RFC 8094: DNS over Datagram Transport Layer Security (DTLS)

Date de publication du RFC : Février 2017
Auteur(s) du RFC : T. Reddy (Cisco), D. Wing, P. Patil (Cisco)
Réalisé dans le cadre du groupe de travail IETF dprive
Première rédaction de cet article le 2 mars 2017

Le DNS fonctionne traditionnellement surtout sur UDP, notamment pour minimiser la latence : quand on veut une réponse DNS, on la veut rapidement. Dans le cadre du projet « DNS et vie privée », le choix avait été fait de chiffrer le trafic DNS avec TLS (RFC 7858), imposant ainsi l'usage de TCP. Certains pensaient quand même qu'UDP était bien adapté au DNS et, puisqu'il existe une version de TLS adaptée à UDP, DTLS, ce serait une bonne idée de l'utiliser pour chiffrer le DNS. C'est ce que décrit ce nouveau RFC (qui ne semble pas avoir un avenir brillant, peu de gens sont intéressés).

De toute façon, il est très possible que le DNS utilise de plus en plus TCP, et le RFC 7766 allait dans ce sens, demandant davantage de la part des mises en œuvre de DNS sur TCP. Mais, bon, il est toujours bon d'essayer des alternatives, d'où ce RFC, dans l'état « Expérimental ». Outre les RFC déjà cités, il est recommandé, avant de le lire, de prendre connaissance du RFC 7626, qui décrit les problèmes de vie privée que pose le DNS, et le RFC 6347, qui normalise DTLS (bien moins connu que son copain TLS, et peu utilisé jusqu'à présent, à part pour WebRTC).

Les motivations pour explorer une alternative au DNS-sur-TLS du RFC 7858 sont :

  • TCP souffre du « head of line blocking » où la perte d'un seul paquet empêche de recevoir tous ceux qui suivent, même s'ils sont bien arrivés, tant que le paquet perdu n'est pas retransmis. DNS-sur-DTLS sera donc peut-être meilleur sur des réseaux qui perdent pas mal de paquets.
  • Dans certaines conditions, l'établissement d'une session est plus rapide avec DTLS qu'avec TLS. (Rappelez-vous toutefois que le RFC 7766 exige des sessions TCP persistentes : pas question d'établir une session par requête DNS !) Reprendre une session TLS peut ne prendre qu'un aller-retour avec DTLS, alors que TLS devra attendre l'établissement de la connexion TCP (le RFC 7413 changera peut-être les choses, mais TLS et DTLS 1.3 obligeront également à réviser ce raisonnement.)

De même qu'un serveur et un client DNS ne peuvent pas se contenter d'UDP (pour pouvoir envoyer des données de grande taille, il faudra de toute façon passer à TCP), DNS-sur-DTLS ne peut pas suffire seul, et il faudra donc que les clients et serveurs aient également DNS-sur-TLS.

La spécification de DNS-sur-DTLS est dans la section 3 de notre RFC. DNS-sur-DTLS va tourner, comme DNS-sur-TLS, sur le port 853 (sauf accord préalable entre client et serveur, s'ils sont adultes et consentants). Un client peut déterminer si le serveur gère DNS-sur-DTLS en envoyant un message DTLS ClientHello vers le port 853. En l'absence de réponse, le client réessaie, puis laisse tomber DTLS. Selon sa configuration (plus ou moins paranoïaque), le client va alors tenter le DNS habituel en clair, ou bien complètement renoncer. En tout cas, interdiction d'utiliser le port 853 pour transmettre des messages DNS en clair. L'utilisation de ce port sur UDP implique DTLS.

Si, par contre, le serveur répond et qu'une session DTLS est établie, le client DNS-sur-DTLS authentifie le serveur avec les mêmes méthodes que pour TLS, en suivant les bonnes pratiques de sécurité de TLS (RFC 7525) et les profils d'authentification de DNS-sur-TLS décrits dans un futur RFC (quasiment terminé, à l'heure où j'écris). Une fois que tout cela est fait, les requêtes et réponses DNS sont protégées et les surveillants sont bien embêtés, ce qui était le but.

DTLS tourne sur UDP et reprend sa sémantique. Notamment, il est parfaitement normal qu'une réponse arrive avant une autre, même partie plus tôt. Le client DNS-sur-DTLS ne doit donc pas s'étonner et, pour faire correspondre les requêtes et les réponses, il doit, comme avec le DNS classique sur UDP, utiliser le Query ID ainsi que la question posée (qui est répétée dans les réponses, dans la section Question).

Pour ne pas écrouler le serveur sous la charge, le client ne devrait créer qu'une seule session DTLS vers chaque serveur auquel il parle, et y faire passer tous les paquets. S'il y a peu de requêtes, et que le client se demande si le serveur est toujours là, il peut utiliser l'extension TLS du « battement de cœur » (RFC 6520), qui peut également servir à rafraichir l'état d'un routeur NAT éventuel. Le RFC recommande aux serveurs DNS-sur-DTLS un délai d'au moins une seconde en cas d'inutilisation de la session, avant de raccrocher. Le problème est délicat : si ce délai est trop long, le serveur va garder des ressources inutiles, s'il est trop court, il obligera à refaire le travail d'établissement de session trop souvent. En tout cas, le client doit être prêt à ce que le serveur ait détruit la session unilatéralement, et doit la réétablir s'il reçoit l'alerte DTLS qui lui indique que sa session n'existe plus.

Un petit mot sur les performances, maintenant, puisque rappelons-nous que le DNS doit aller vite (section 4). L'établissement d'une session DTLS peut nécessiter d'envoyer des certificats, qui sont assez gros et peuvent nécessiter plusieurs paquets. Il peut donc être utile d'utiliser les clés brutes (pas de certificat) du RFC 7250, ou bien l'extension TLS Cached Information Extension (RFC 7924).

Dans le cas d'un lien stub resolver vers résolveur, le serveur DNS parle à beaucoup de clients, chaque client ne parle qu'à très peu de serveurs. L'état décrivant les sessions DTLS doit donc plutôt être gardé chez le client (RFC 5077). Cela permettra de réétablir les sessions DTLS rapidement, sans pour autant garder d'état sur le serveur.

Le DNS est la principale application qui se tape les problèmes de PMTU (Path MTU, la MTU du chemin complet). Les réponses DNS peuvent dépasser les 1 500 octets magiques (la MTU d'Ethernet et, de facto, la PMTU de l'Internet). DTLS ajoute au moins 13 octets à chaque paquet, sans compter l'effet du chiffrement. Il est donc impératif (section 5) que clients et serveurs DNS-sur-DTLS gèrent EDNS (RFC 6891) pour ne pas être limité par l'ancien maximum DNS de 512 octets, et que les serveurs limitent les paquets DTLS à la PMTU (RFC 6347).

Contrairement au DNS classique, où chaque requête est indépendante, toute solution de cryptographie va nécessiter un état, l'ensemble des paramètres cryptographiques de la session. L'anycast, qui est répandu pour le DNS, ne pose donc pas de problème au DNS classique : si le routage change d'avis entre deux requêtes, et que la seconde requête est envoyée à un autre serveur, aucun problème. Avec DTLS, ce n'est plus le cas (section 6 du RFC) : le deuxième serveur n'a pas en mémoire la session cryptographique utilisée. Le serveur qui la reçoit va répondre avec une alerte TLS fatale (la méthode recommandée) ou, pire, ne pas répondre. Dans les deux cas, le client doit détecter le problème et réétablir une session cryptographique. (À noter que l'alerte TLS n'est pas authentifiée et ne peut donc pas être utilisée comme seule indication du problème. C'est d'ailleurs pareil pour d'éventuels messages d'erreur ICMP.) Le cas est donc proche de celui où le serveur ferme la session unilatéralement, et la solution est la même : le client doit toujours être prêt à recommencer l'ouverture de session DTLS.

Un point de sécurité, pour finir (section 9). Le RFC recommande l'utilisation de l'extension TLS « agrafage OCSP » (RFC 6066, section 8), notamment pour éviter la grosse fuite d'information que représente OCSP.

Il n'existe aucune mise en œuvre de DNS-sur-DTLS, et aucune n'est prévue. L'avenir de cette expérimentation est... incertain, à moins qu'un·e courageu·x·se développeu·r·se ne s'y mette ?

Téléchargez le RFC 8094

L'article seul

RFC 8033: Proportional Integral Controller Enhanced (PIE): A Lightweight Control Scheme to Address the Bufferbloat Problem

Date de publication du RFC : Février 2017
Auteur(s) du RFC : R. Pan, P. Natarajan, F. Baker (Cisco Systems), G. White (CableLabs)
Réalisé dans le cadre du groupe de travail IETF aqm
Première rédaction de cet article le 1 mars 2017

Mais c'est quoi, ce bufferbloat (obésité du tampon ?) et pourquoi est-ce mauvais ? Le bufferbloat désigne la tendance à mettre dans les routeurs des tampons d'entrée/sortie de plus en plus gros. Cette tendance vient de la baisse du prix des mémoires, et d'un désir de pouvoir encaisser les brusques pics de trafic (bursts) qui sont fréquents sur l'Internet. Mais le bufferbloat a une conséquence négative : la latence augmente, puisque le paquet risque de devoir attendre longtemps dans un tampon qui, une fois rempli, aura du mal à se vider. Ce RFC propose donc un mécanisme de gestion des files d'attente, PIE (Proportional Integral controller Enhanced) où le routeur surveille la latence des paquets dans les tampons d'entrée/sortie, et jette des paquets, même si le tampon n'est pas plein, pour limiter la latence. Le paquet perdu dira en effet aux émetteurs de ralentir.

La latence est particulièrement à surveiller dans le cas d'applications fortement interactives comme les jeux en ligne ou la vidéoconférence. On cherche donc à diminuer la latence, pour fournir une meilleure qualité de service aux utilisateurs. PIE a fait l'objet d'analyses théoriques, de simulations, puis de mise en œuvre dans le noyau Linux, et semble aujourd'hui une solution intéressante. PIE est une solution purement locale au routeur, et ne pose donc pas de problèmes d'interopérablité : les autres routeurs avec lesquels on parle n'ont pas besoin de participer.

L'un des problèmes centraux de l'Internet a toujours été la congestion. Les paquets arrivent quand ils veulent, et peuvent dépasser la capacité du réseau. Deux solutions pour un routeur, jeter les paquets (IP est prévu pour cela, il travaille en mode datagramme), et attendre que les couches supérieures comme TCP s'en aperçoivent et ralentissent, ou bien deuxième solution, mettre les paquets dans un tampon, en attendant de pouvoir les envoyer. Ce tampon va permettre de lisser un trafic Internet qui est très irrégulier. En pratique, les deux solutions doivent être déployées : le tampon a une taille finie et, s'il est plein, il faut bien se résigner à jeter des paquets.

Comme la perte de paquets entraine un ralentissement du transfert de données (TCP va automatiquement diminuer le débit), il existe une forte demande pour limiter cette perte. La baisse des prix des mémoires permet de satisfaire cette demande, en augmentant la taille des tampons. (Voir le site Web consacré au « bufferbloat », qui contient notamment une bonne introduction au problème.)

L'effet pervers de cette augmentation de taille est que les protocoles comme TCP, ne voyant pas de perte de paquets, vont continuer à augmenter leur débit, et envoyer plein de paquets jusqu'à ce que, le tampon étant plein, le routeur commence à jeter des paquets, calmant TCP. Mais, à ce moment, il est trop tard, le tampon est plein et risque de rester plein longtemps, l'émetteur continuant à envoyer des paquets, même si c'est à un rythme réduit. Les paquets vont donc patienter dans le tampon, augmentant la latence. Et plus le tampon est grand, plus on aggrave la latence. On est donc passé de Charybde en Scylla : pour éviter les pertes de paquets, qui diminuent le débit, on a augmenté la latence. (On voit d'ailleurs que la notion de performance, dans les réseaux, est une notion compliquée. C'est pour cela que des termes flous et passe-partout comme « vitesse » ne devraient pas être employés.)

Un système de gestion de la file d'attente (AQM) va permettre de mieux contrôler le problème, en essayant de faire en sorte que les pics soudains d'activité puissent passer, tout en limitant la latence pour les transferts de longue durée. Un exemple de mécanisme d'AQM est RED, initialement proposé dans le RFC 2309 il y a dix-huit ans. RED a deux limites, il nécessite un réglage manuel de ses paramètres, et il agit sur la longueur de la file d'attente, pas sur la latence. C'est entre autre pour cela que le RFC 7567 avait demandé à ce que de nouveaux mécanismes d'AQM soient développés.

L'algorithme de ce RFC, PIE, se veut, comme RED, simple et facile à mettre en œuvre. Comme RED, son principal moyen d'action est de jeter, de manière partiellement aléatoire, des paquets avant qu'ils ne soient mis dans la file d'attente. Contrairement à RED, il agit sur la latence, pas sur la longueur de la file d'attente.

Les objectifs de PIE sont décrits dans la section 3 du RFC :

  • Contrôler la latence, le paramètre qui est réellement important pour les applications,
  • Essayer d'utiliser le réseau au mieux de sa capacité (si on jette trop de paquets, TCP va tellement ralentir que, certes, les tampons seront vides et la latence excellente, mais le réseau ne sera plus utilisé à fond),
  • Simple à programmer et déployer (pas de réglage manuel des paramètres).

La section 4 du RFC décrit PIE, et c'est la section à lire si vous voulez mettre en œuvre PIE dans un routeur, ou simplement le comprendre complètement. L'algorithme effectue trois tâches :

  • Jeter des paquets aléatoirement, avec une certaine probabilité, lors de l'arrivée dans la file d'attente,
  • Mettre à jour automatiquement en permanence cette probabilité,
  • Calculer la latence (puisque c'est elle qu'on veut minimiser).

La description complète originale figure dans l'article de Pan, R., Natarajan, P. Piglione, C., Prabhu, M.S., Subramanian, V., Baker, F. Steeg et B. V., « PIE: A Lightweight Control Scheme to Address the Bufferbloat Problem » en 2013. Cet algorithme suit les principes de stabilité de théorie du contrôle.

Dans cette section 4, notre RFC présente l'algorithme PIE sous forme de texte et de pseudo-code. La première tâche (section 4.1), jeter les paquets entrants selon une certaine probabilité (PIE->drop_prob_) va s'exprimer :

      //Safeguard PIE to be work conserving
      if ( (PIE->qdelay_old_ < QDELAY_REF/2 && PIE->drop_prob_ < 0.2)  
   	  || (queue_.byte_length() <= 2 * MEAN_PKTSIZE) ) {  
           return ENQUE;
          randomly drop the packet with a probability PIE->drop_prob_. 


La première branche du if est là pour éviter du travail inutile : si la probabilité de jeter un paquet est faible, ou bien si la file d'attente est loin d'être pleine (moins de deux paquets en attente), ou bien si la latence est bien plus faible que la latence visée, dans ces cas, on le jette rien. C'est le fonctionnement idéal du routeur, lorsque la congestion n'est qu'une menace lointaine.

La deuxième tâche, calculer automatiquement la probabilité de jeter un paquet, est plus délicate (section 4.2). Il faut connaitre la latence mais aussi la tendance (est-ce que la latence tend à diminuer ou bien à augmenter). C'est ce qu'on nomme le contrôleur Proportional Integral qui a donné son nom à l'algorithme PIE. La formule de base (voir le pseudo-code complet dans le RFC, notamment dans l'annexe A) est que la probabilité est la latence (current_qdelay) multipliée par un coefficient (alpha), augmentée de la différence entre la latence actuelle et la latence précédente (et, donc, si la latence diminue, la probabilité sera diminuée) :

p = alpha*(current_qdelay-QDELAY_REF) + 

Et la troisième tâche, le calcul de la latence, est fait en suivant la loi de Little (section 4.3) :

current_qdelay = queue_.byte_length()/dequeue_rate; 

Cette formule est une estimation de la latence. On peut aussi la mesurer directement (mais cela fait plus de travail pour le routeur), par exemple en ajoutant une estampille temporelle aux paquets entrants et en la lisant à la sortie.

Ce pseudo-code n'est encore qu'une approximation du vrai algorithme. L'un des gros problèmes de tout système de gestion de la file d'attente est que le trafic Internet est sujet à de brusques pics où un grand nombre de paquets arrive en peu de temps. Cela va remplir la file et augmenter la latence, mais cela ne veut pas dire qu'il faille subitement augmenter la probabilité d'abandon de paquets (section 4.4). Donc, la première tâche, jeter certains paquets, devient :

      if PIE->burst_allowance_ > 0 enqueue packet;
      else randomly drop a packet with a probability PIE->drop_prob_.

      if (PIE->drop_prob_ == 0 and current_qdelay < QDELAY_REF/2 and PIE->qdelay_old_ < QDELAY_REF/2)
          PIE->burst_allowance_ = MAX_BURST;


Et dans la seconde, le calcul de la probabilité d'abandon de paquets, on ajoute :

PIE->burst_allowance_ = max(0,PIE->burst_allowance_ - T_UPDATE);

Cette fois, on a un PIE complet. Mais on peut, optionnellement, y ajouter certains éléments (section 5 du RFC). Le plus évident est, au lieu de jeter le paquet, ce qui fait qu'il aura été émis et transmis par les routeurs amont pour rien, de marquer les paquets avec ECN (RFC 3168). La première tâche regarde donc si le flot de données gère ECN et utilise cette possibilité dans ce cas, au lieu de jeter aveuglément :

        if PIE->drop_prob_ < mark_ecnth && ecn_capable_packet:
            mark packet;
            drop packet;

Le trafic réseau varie beaucoup dans le temps. La plupart du temps, si le réseau est bien dimensionné, il n'y a pas de problème et il serait dommage que PIE jette au hasard des paquets quand on n'est dans cette phase heureuse. Un autre ajout utile à PIE est donc une désctivation automatique quand la file d'attente est peu remplie. Un des avantages de couper complètement PIE (par rapport à simplement décider de ne pas jeter les paquets) est de gagner du temps dans le traitement des paquets.

Pour réactiver PIE quand la congestion commence, c'est un peu plus compliqué. Si PIE est coupé, il n'y a plus de calcul de la latence, et on ne peut donc pas utiliser une augmentation de la latence pour décider de remettre PIE en marche. Le RFC suggère de remettre PIE en route dès qu'on passe au-dessus d'un tiers d'occupation de la file d'attente.

Autre question délicate, les problèmes que crée le hasard. Par défaut, PIE prend ses décisions en jetant les dés. Si la latence est importante, indiquant qu'on approche de la congestion, PIE jette des paquets au hasard. Mais le hasard n'est pas prévisible (évidemment). Et il ne mène pas à une répartition uniforme des pertes de paquets. Il se peut qu'aucun paquet ne soit jeté pendant longtemps, ce qui fait que le routeur ne réagira pas à l'augmentation de la latence. Mais il se peut aussi qu'un massacre de paquets se produise à certains moments. L'utilisation du hasard mène forcément à des « séries noires » (ou à des « séries blanches »). Notre RFC propose donc un mécanisme (optionnel) de « dé-hasardisation », où un nouveau paramètre augmente avec la probabilité d'abandon de paquet, et est remis à zéro lorsqu'on jette un paquet. La décision de laisser tomber un paquet n'est prise que lorsque ce paramètre est entre deux valeurs pré-définies.

La section 6 du RFC se penche sur les problèmes concrets de mise en œuvre (programmeurs, on pense à vous). PIE peut être mis en œuvre en logiciel ou bien en matériel (sur beaucoup de routeurs, la mise en file d'attente est typiquement « plus logicielle » que le retrait de la file). PIE est simple, et peut être programmé de manière très économique (ou plus coûteuse si on met une estampille temporelle à chaque paquet, ce qui permet de mieux mesurer la latence, mais nécessite davantage de mémoire).

La deuxième tâche de PIE, recalculer la probabilité d'abandon, se fait typiquement en parallèle avec le traitement de la file d'attente. Vu le rythme d'entrée et de sortie des paquets dans un routeur moderne, ce sont des milliers de paquets qui sont passés entre deux recalculs. Le routeur ne pourra donc pas réagir instantanément.

Comme tous les bons algorithmes, PIE est évidemment plombé par un brevet, en l'occurrence deux brevets de Cisco. Cette entreprise a promis une licence gratuite et sans obligations (mais avec la classique clause de représailles, annulant cette licence si quelqu'un essaie d'utiliser ses brevets contre Cisco).

Aujourd'hui, Linux, FreeBSD (voir la page Web du projet) et d'autres mettent en œuvre PIE.

Téléchargez le RFC 8033

L'article seul

RFC 8081: The "font" Top-Level Media Type

Date de publication du RFC : Février 2017
Auteur(s) du RFC : C. Lilley (W3C)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF justfont
Première rédaction de cet article le 1 mars 2017

Les types de contenu, servant à indiquer le type des données envoyées (par le Web, par le courrier, etc) sont composés de deux parties, un type de premier niveau (top level type, ou type proprement dit, c'est la catégorie des données) et un sous-type (il indique le format des données). Type et sous-type sont séparés par une barre oblique. Par exemple, image/png est un type MIME identifiant une image au format PNG. Des nouveaux sous-types sont enregistrés très souvent, c'est un événement banal. Mais des nouveaux types de premier niveau sont bien plus rares. Ce RFC en décrit un, le type font/, qui sert à identifier les formats pour des polices de caractères. Ainsi, on pourra envoyer un fichier de polices au format TTF en l'étiquetant font/ttf. (Notre RFC procède également à l'enregistrement de plusieurs sous-types pour des formats de polices particuliers.)

Une police de caractères, c'est une description de comment représenter visuellement un ensemble de caractères, description qu'un programme peut lire et comprendre. Il existe bien des façons de faire cette représentation. Les premiers formats de polices numériques étaient matriciels mais on est depuis passés à des formats vectoriels, qui permettent des changements de taille à volonté. Ces descriptions de caractères peuvent être distribuées via l'Internet et la question se pose alors du type de média à utiliser. En pratique, cela a souvent été application, un type un peu fourre-tout. On trouve ainsi enregistré, par exemple, un application/font-woff. Le RFC 6838, sur l'enregistrement des types et sous-types de contenus, permet (dans sa section 4.2.7, qui ajoute « Such cases are expected to be quite rare ») l'enregistrement d'un nouveau type de premier niveau. C'est ainsi que ce RFC 8081 crée font/.

Le besoin provient entre autres de l'usage de plus en plus important des Web fonts. C'est ansi qu'HTTP Archive a vu passer le pourcentage de sites Web utilisant cette technique de 1 % en 2010 à 50 % en 2015. L'analyse de Kuetell montrait une certaine confusion chez les utilisateurs quant au type MIME à utiliser pour ces polices. Certains utilisaient le type de premier niveau font/ avant même son enregistrement officiel et on voyait donc déjà des font/truetype pour le format TrueType. D'autres se servaient d'application/ pour des application/octet-stream (fichier binaire quelconque) ou des application/x-font-ttf (utilisant le préfixe x-, pourtant abandonné par le RFC 6648). On voit même des text/plain pour des ressources pourtant clairement binaires... Les rares types officiellement enregistrés, comme application/font-woff, enregistré par un groupe du W3C, sont peu utilisés.

Au fait, pourquoi est-ce qu'application/ est une mauvaise idée ? Une des principales raisons est qu'il est regardé avec suspicion par les logiciels de filtrage, qui se méfient de la capacité de ces fichiers à transporter du logiciel malveillant. (Certains formats de police incluent un langage de Turing, et peuvent donc offrir des possibilités insoupçonnées…) Ensuite, en l'absence d'un type de premier niveau, il n'était pas possible de définir un jeu commun de paramètres précisant le type. Enfin, les polices de caractères ne sont pas des logiciels et posent des problèmes spécifiques, notamment de licence. Bref, il fallait un type pour les formats de polices.

Ah, et puisque j'ai parlé de sécurité, la section 3 du RFC fait le point sur les problèmes que peuvent poser les polices de ce côté. Un fichier décrivant une police contient des données, mais aussi des programmes (hinting instructions) pour les opérations de rendu les plus sophistiquées. Par exemple, quand on agrandit un caractère, il ne faut pas agrandir uniformément selon toutes les dimensions ; ou bien certaines caractéristiques d'un caractère dépendent des caractères qui l'entourent. Bref, le rendu est une chose trop compliquée pour être spécifié sans un langage de programmation. C'est par exemple ce qu'on trouve dans les polices TrueType (cf. l'article de Wikipédia). Bien sûr l'exécution de ces « programmes » se fait en général dans un bac à sable, et ils n'ont pas accès à l'extérieur, mais certaines attaques restent possibles, par exemple des attaques par déni de service visant à bloquer le moteur de rendu. Les langages utilisés sont en général trop riches pour que des protections simples suffisent.

Et même si on se limite aux données, la plupart des formats (comme SFNT) sont extensibles et permettent l'ajout de nouvelles structures de données. Cette extensibilité est une bonne chose mais elle présente également des risques (par exemple, elle facilite la dissimulation de données dans les fichiers de polices).

Bon, je vous ai assez fait peur avec les risques de sécurité, place à l'enregistrement de font/ à l'IANA (section 4 du RFC). font/ n'indique pas un format particulier, mais une catégorie de contenu. Le format sera indiqué dans le sous-type et c'est là seulement que le logiciel qui reçoit ce contenu saura s'il peut en faire quelque chose d'utile. (Le RFC suggère que les sous-types inconnus devraient être traités comme du binaire quelconque, comme s'ils étaient application/octet-stream.) Six sous-types sont enregistrés par notre RFC.

On peut utiliser un identificateur de fragment (RFC 3986, section 3.5, cet identificateur est le truc après le croisillon dans un URI), pour désigner une police particulière au sein d'une collection présente dans les données envoyées. L'identificateur est le nom PostScript. Attention, certains caractères peuvent être utilisés dans un nom PostScript mais interdits pour un identificateur de fragment, et doivent donc être échappés avec la notation pour-cent. Par exemple, l'identificateur de la police Caret^stick sera #Caret%5Estick.

Le RFC enregistre plusieurs sous-types. Si on veut en ajouter au registre des polices, il faut suivre les procédures du RFC 6838. Il est recommandé que la spécification du format soit librement accessible (ce qui n'est pas évident dans ce milieu).

Le RFC se termine avec les six sous-types de font/ officiellement enregistrés. D'abord, sfnt pour le format générique SFNT. Il prend des paramètres optionnels, outlines (qui prend comme valeur TTF, CFF ou SVG) et layout (valeurs OTL, AAT et SIL). On pourra donc écrire, par exemple, font/sfnt; layout=SIL. Ce font/sfnt remplace l'ancien type enregistré, application/font-sfnt. Notez que la spécification de ce format est la norme ISO ISO/IEC 14496-22, dite « Open Font Format ».

SFNT est un format générique, qui sera sans doute rarement utilisé tel quel. On verra plutôt ttf ou otf.

Un exemple d'un format spécifique est en effet TrueType. Ce sera le sous-type ttf. Il aura également un paramètre optionnel layout (mêmes valeurs possibles). On pourra donc voir dans une réponse HTTP Content-Type: font/ttf.

Troisième sous-type enregistré, otf pour OpenType.

On trouve aussi un sous-type collection pour mettre plusieurs polices ensemble.

Viennent enfin WOFF versions 1 (woff) et 2 (woff2). Il s'agit cette fois d'une norme W3C. Ce nouveau type font/woff remplace l'ancien application/font-woff.

Voilà, c'est tout, le nouveau type de premier niveau font est désormais inclus dans le registre IANA des types de premier niveau, et les polices enregistrées sont dans cet autre registre IANA.

Téléchargez le RFC 8081

L'article seul

RFC 8098: Message Disposition Notification

Date de publication du RFC : Février 2017
Auteur(s) du RFC : T. Hansen (AT&T Laboratories), A. Melnikov (Isode)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF appsawg
Première rédaction de cet article le 1 mars 2017

Une demande fréquente des utilisateurs du courrier électronique est d'avoir un mécanisme permettant de savoir si et quand le message a été lu par le destinataire. Comme toutes les demandes des utilisateurs, il ne faut pas forcément la satisfaire sans réfléchir (elle pose des gros problèmes de vie privée et, en outre, elle ne garantit pas que le message a été traité, juste que le logiciel l'a affiché). Ce n'est pas par hasard que cette fonction « accusé de réception » était souvent présente (et mise en avant par les vendeurs) pour les systèmes de messagerie conçus pour des environnements très bureaucratiques (le RFC cite l'antédiluvien X.400). Mais, bon, si les gens y tiennent, cette possibilité existe dans la norme : ce nouveau RFC spécifie un mécanisme permettant de signaler qu'on souhaite un tel accusé de réception, ainsi qu'un format structuré (lisible par un programme comme le MUA) pour les accusés de réception qui seront (peut-être) envoyés. Ces accusés de réception sont appelés MDN pour Message Disposition Notification. Ce RFC remplace son prédécesseur, le RFC 3798.

Donc, résumé général du fonctionnement de ce système : l'émetteur d'un message qui veut un accusé de réception met un en-tête Disposition-Notification-To: dans son message. Le récepteur, s'il le désire, répondra à cette demande lors de la lecture du message, en envoyant un message de type MIME message/disposition-notification (a priori situé à l'intérieur d'un rapport plus général, de type multipart/report, cf. RFC 6522). Tout ceci est sous un format structuré, donc peut être traité par un programme, typiquement le MUA. Voilà, vous connaissez l'essentiel de ce RFC. Place aux détails.

À quoi servent les MDN (Message Disposition Notification, un concept plus large que celui d'accusé de réception) ? Voici le cahier des charges proposé par notre RFC :

  • Indiquer ce qu'il est advenu du message après la réception physique (lu, imprimé, détruit),
  • Permettre d'associer un message à son devenir (le MDN contient les informations permettant la jointure avec les messages envoyés),
  • Transmettre de l'information sur le devenir des messages entre systèmes de messagerie différents (un cas devenu rare aujourd'hui, mais qui était plus crucial lors de la sortie du premier RFC sur cette technique, en 1998, cf. la section 8 pour ces passerelles),
  • Donner de l'information aux programmes, pas seulement aux humains (pas uniquement du texte non formaté, donc),
  • Être indépendant de la langue naturelle utilisée par les humains,
  • Être extensible, car on ne sait jamais.

Première partie de la norme, la demande d'un MDN (section 2). L'émetteur le fait en ajoutant dans son message un en-tête Disposition-Notification-To: indiquant les adresses auxquelles envoyer le MDN. Par exemple :


Le risque d'utilisation de ce truc pour bombarder de message un tiers innocent est évident. C'est pour cela que le RFC recommande d'ignorer cet en-tête si l'adresse indiquée ne coïncide pas avec celle stockée dans l'en-tête Return-Path: (voir section 6.4). Dans tous les cas, rappelez-vous bien que le logiciel à la réception est libre de faire ce qu'il veut. Il peut estimer que ces MDN ne servent à rien et ignorer les Disposition-Notification-To:, il peut demander une autorisation à l'utilisateur il peut envoyer le MDN de manière totalement automatique (après les vérifications de vraisemblance comme celle du Return-Path:), etc.

Deuxième partie de la norme, le format du MDN lui-même (section 3 du RFC). La réponse est dans un message de type multipart/report (type défini dans le RFC 6522), avec le type de rapport (paramètre report-type) disposition-notification. Le MDN lui-même a deux ou trois parties : une première partie est du texte libre, lisible par un humain, une deuxième est structurée, et de type MIME message/disposition-notification, la troisième partie est optionnelle et est le message auquel on « répond ».

La deuxième partie du MDN est la plus intéressante. Son corps est composé de plusieurs champs nom: valeur, dont deux sont obligatoires, Final-Recipient: et Disposition: qui indique ce qui est arrivé au message. Parmi les autres champs, notez le Reporting-UA:, indiquant le logiciel qui a répondu, et dont le RFC recommande qu'il ne soit pas trop détaillé, car il donne des informations qui peuvent être utiles à un éventuel attaquant. Comme Reporting-UA:, le champ Original-Message-ID: n'est pas obligatoire mais il est très utile : c'est lui qui permet à l'émetteur du message original de faire la jointure entre ce qu'il a envoyé et le MDN reçu. (Il n'est pas obligatoire car le message original n'a pas forcément un Message-ID:. Mais, s'il en a un, il faut inclure Original-Message-ID: dans le MDN.)

Le champ le plus important est sans doute Disposition:. Il indique ce qui est arrivé au message original (disposition type) : a-t-il été affiché à un utilisateur (displayed, ce qui ne garantit pas du tout qu'il soit arrivé au cerveau de l'utilisateur), traité sans être montré à un utilisateur (processed), effacé (deleted) ? Ce champ Disposition: indique aussi (disposition mode) si le sort du message a été décidé par un être humain ou bien automatiquement (par exemple par Sieve), et si le MDN a été généré suite à une autorisation explicite ou bien automatiquement. Notez bien (et c'est la principale raison pour laquelle les accusés de réception sont une fausse bonne idée) que la seule façon d'être sûr que le message aura été traité par son destinataire, est de recevoir une réponse explicite et manuelle de sa part.

Enfin, le champ Error: sert à transporter des messages... d'erreur.

Voici un exemple complet de MDN, tiré de la section 9 :

Date: Wed, 20 Sep 1995 00:19:00 (EDT) -0400
From: Joe Recipient <>
Message-Id: <>
Subject: Re: First draft of report
To: Jane Sender <>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=disposition-notification;

Content-type: text/plain

The message sent on 1995 Sep 19 at 13:30:00 (EDT) -0400 to Joe
Recipient <> with subject "First draft of
report" has been displayed.

This is no guarantee that the message has been read or understood.

Content-type: message/disposition-notification

Reporting-UA:; Foomail 97.1
Original-Recipient: rfc822;
Final-Recipient: rfc822;
Original-Message-ID: <>
Disposition: manual-action/MDN-sent-manually; displayed

Content-type: message/rfc822

[original message optionally goes here]


Notez la première partie, en langue naturelle (ici en anglais), la seconde, avec les informations structurées (ici, le destinataire a affiché le message - manual-action ... displayed - puis autorisé/déclenché manuellement l'envoi du MDN - MDN-sent-manually), et la présence de la troisième partie, qui est optionnelle.

Un peu de sécurité pour finir le RFC. D'abord, évidemment, il ne faut pas accorder trop d'importance aux MDN. Ils peuvent être fabriqués de toutes pièces, comme n'importe quel message sur l'Internet. Ensuite, il faut faire attention à la vie privée des utilisateurs. Le destinataire n'a pas forcément envie qu'on sache si et quand il a lu un message ! Le destinataire, ou son logiciel, ont donc parfaitement le droit de refuser d'envoyer un MDN (ce qui diminue encore l'intérêt de cette technique, qui était déjà très faible). Même des informations inoffensives à première vue, comme le contenu du champ Disposition: peuvent être considérées comme sensibles. Si on configure Sieve pour rejeter (RFC 5429) automatiquement tous les messages d'une certaine personne, on n'a pas forcément envie qu'elle le sache. Le RFC précise donc qu'on peut envoyer manual-action/MDN-sent-manually dans ce cas, pour cacher le fait que c'était automatique.

Quels sont les changements depuis le précédent RFC, le RFC 3798 ? Ils sont résumés dans l'annexe A. Tout ce qui touche à la vie privée a été sérieusement renforcé (les MDN sont très indiscrets). Les champs commençant par un X- ont été supprimés de la spécification, suivant le RFC 6648. La grammaire a été corrigée (plusieurs bogues et ambiguïtés).

En pratique, les MDN ne semblent guère utilisés dans l'Internet et ont peu de chance de marcher. Je note par exemple qu'aussi bien le MUA Unix mutt que le service Gmail semblent les ignorer complètement. Mais d'autres logiciels ont cette fonction.

Téléchargez le RFC 8098

L'article seul

Les conséquences techniques de l'interception HTTPS en entreprise

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

Le 28 février 2017, à la conférence NDSS 17 à San Diego, Zakir Durumeric a présenté les conclusions de la recherche faite avec Zane Ma, Drew Springall, Richard Barnes, Nick Sullivan, Elie Bursztein, Michael Bailey, J. Alex Halderman et Vern Paxson, « The Security Impact of HTTPS Interception ». Ils montraient que la pratique, très répandue en entreprise, de l'interception (en fait, le détournement) des sessions HTTPS des utilisateurs, outre son côté immoral, a de graves conséquences pour la sécurité.

De quoi s'agit-il ? Normalement, la session HTTPS est protégée de bout en bout. Le client se connecte au serveur et le protocole TLS se fait directement entre client et serveur. Les intermédiaires (par exemple les routeurs) ne voient qu'un flux chiffré qu'ils ne peuvent pas comprendre (confidentialité via le chiffrement), ni modifier (intégrité). Dans un certain nombre d'entreprises et d'organisations, par exemple étatiques, la direction souhaite au contraire pouvoir examiner le trafic des employés, par exemple pour savoir qui écrit au Canard Enchaîné. La technique pour cela consiste à intercepter le trafic HTTPS, le déchiffrer, et le rechiffrer avec le serveur. Lorsqu'il sera en clair, dans l'équipement de surveillance, on pourra l'examiner.

Mais, attendez, dit la lectrice qui connait TLS (RFC 5246) : ça ne peut pas marcher. La session n'est pas juste chiffrée, elle est authentifiée. Le serveur doit présenter un certificat, et il chiffre avec la clé contenue dans ce certificat. L'équipement d'interception ne peut pas montrer un certificat qui convienne.

Le truc réside dans une énorme faille du système X.509 : n'importe quelle autorité de certification peut produire un certificat pour n'importe quel nom de domaine, même si le titulaire de ce nom a choisi une autre AC. Il suffit donc d'avoir une AC à sa disposition dans le magasin de certificat de la machine. C'est ainsi par exemple que le ministère des finances avait fait un vrai/faux certificat pour

Je ne parlerai pas ici des aspects moraux, politiques ou juridiques de la surveillance via interception HTTPS. L'article des chercheurs qui a fait l'objet de l'exposé d'aujourd'hui se focalisait sur les problèmes techniques. Ils ont étudié cette interception des deux côtés : en observant le trafic chez certains serveurs Web, et en étudiant certaines des boîtes noires qui font cette interception. Côté serveur, pour voir si le trafic est intercepté, ils regardaient surtout s'il y avait une différence entre le User-Agent: HTTP (RFC 7231, section 5.5.3) annoncé et les paramètres TLS. Si un navigateur se présente comme étant Firefox mais annonce l'extension « battement de cœur » du RFC 6520, on est sûr qu'il s'agit d'une interception : Firefox n'a jamais mis en œuvre cette extension. Des tas d'autres heuristiques peuvent être utilisées, comme l'ordre des extensions TLS dans le ClientHello du client.

Leur étude montre que plus de 10 % des sessions HTTPS vers Cloudflare (6 % pour des sites divers de commerce en ligne) sont interceptées, ce qui est assez inquiétant.

Mais il y a bien pire : le système d'interception, on l'a vu, termine la session TLS et en commence une autre avec le serveur. Ce faisant, la totalité des systèmes testés font d'énormes erreurs TLS : ils annoncent des algorithmes de chiffrement abandonnés (RC4, cf. RFC 7465), ou qui n'auraient jamais dû être utilisées (les algorithmes « exportation », délibérement affaiblis), acceptent des certificats expirés, et, parfois, ils ne valident même pas le certificat du serveur ! Ils sont en outre vulnérables à plusieurs attaques TLS connues. Cela est dû au fait que ces boîtes noires utilisent des versions anciennes de bibliothèques TLS, et qu'elles ne les configurent pas proprement. (Ce problème avait déjà été démontré avec les anti-virus.)

Rien d'étonnant à cela : ces boîtes noires sont achetées par des gens qui n'y connaissent rien, qui n'évaluent pas le logiciel, et pour qui la sécurité est un produit qu'on achète. (Je viens de lire un guide « L'essentiel de la sécurité numérique pour les dirigeants » qui recommande de dépenser « de 3 % à 10 % du budget informatique [pour la] cybersécurité », comme si la sécurité dépendait de l'argent dépensé !)

Autre point à noter : ces boîtes noires de surveillance sont toutes en logiciel privateur (et sont donc populaires auprès de dirigeants qui se disent « le logiciel libre, ce n'est pas professionnel et enterprise-grade ») mais utilisent très souvent du logiciel libre en dessous (sans prendre la peine d'utiliser des versions récentes).

Donc, employés, la prochaine fois que vous entendez dire qu'on a déployé l'interception HTTPS pour votre bien, « pour des raisons de sécurité », méfiez-vous : cette pratique diminue la sécurité.

Dans la série des bonnes lectures, notez que l'ANSSI a un guide sur l'interception HTTPS. Notez que le US-CERT a également sonné l'alarme contre ces interceptions HTTPS. La recherche présentée à NDSS portait sur le côté client des boîtiers intercepteurs mais leur côté serveur est tout aussi bogué comme le montre l'exemple BlueCoat.

L'article seul

RFC 8064: Recommendation on Stable IPv6 Interface Identifiers

Date de publication du RFC : Février 2017
Auteur(s) du RFC : F. Gont (SI6 Networks / UTN-FRH), A. Cooper (Cisco), D. Thaler (Microsoft), W. Liu (Huawei Technologies)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF 6man
Première rédaction de cet article le 24 février 2017

Ce RFC parle de vie privée mais il est très court, car il se contente de changer une règle, la nouvelle étant déjà largement acceptée. Désormais, si une machine IPv6 configure son adresse par le système SLAAC, et que cette adresse doit être stable dans le temps, désormais, donc, la méthode recommandée est celle du RFC 7217 et non plus celle, mauvaise pour la vie privée, d'utiliser l'adresse MAC. (Si l'adresse n'a pas besoin d'être stable, aucun changement, la méthode recommandée reste celle du RFC 4941, les adresses temporaires.)

Que veut dire SLAAC, au fait ? Ce mécanisme de configuration d'une adresse IPv6 est normalisé dans le RFC 4862. L'idée est que la machine écoute sur le réseau les annonces faites par les routeurs, apprenant ainsi le·s préfixe·s IP du réseau. Elle ajoute ensuite à ce préfixe un terme, l'identificateur d'interface (IID, cf. RFC 4291), formant ainsi une adresse IPv6 mondiale, et unique (si l'IID est bien choisi). La méthode originelle était de dériver l'IID de l'adresse MAC. Celle-ci est en effet unique et, en prime, son utilisation présente certains avantages (compression des en-têtes du RFC 6775, par exemple). Mais s'en servir soulève plein de problèmes de sécurité et notamment de vie privée : traçabilité des utilisateurs dans le temps, et dans l'espace (si la machine se déplace, elle change de préfixe mais garde le même identificateur d'interface), facilitation du balayage des adresses dans le réseau, etc (cf. RFC 7721). D'une manière générale, réutiliser des identificateurs d'un autre « monde » est une fausse bonne idée, souvent dangereuse en matière de vie privée. Voilà pourquoi ce RFC dit clairement que, désormais, il est fortement déconseillé d'utiliser les adresses MAC. (Plusieurs mises en œuvre d'IPv6, comme celles de Microsoft, avaient déjà cessé, avant même que ce RFC ne soit publié.)

Et ce RFC 7217 qu'il faut désormais suivre, il dit quoi ? Il propose de fabriquer l'identificateur d'interface en condensat une concaténation du préfixe et de diverses valeurs stables. Si on change de réseau, on a une nouvelle adresse (on ne peut donc pas suivre à la trace une machine mobile). Mais, si on reste sur le même réseau, l'adresse est stable.

La section 1 de notre RFC rappelle aussi la différence entre les adresses stables et les autres. Toutes les adresses IP n'ont pas besoin d'être stables. La solution la meilleure pour la vie privée est certainement celle du RFC 4941, des adresses temporaires, non stables (pour de telles adresses, on peut aussi utiliser le système des adresses MAC si elles changent souvent par exemple avec macchanger). Toutefois, dans certains cas, les adresses stables sont souhaitables : l'administration réseaux est plus simple, les journaux sont plus faciles à lire, on peut mettre des ACL, on peut avoir des connexions TCP de longue durée, etc. Et, bien sûr, si la machine est un serveur, ses adresses doivent être stables. Il y a donc une place pour une solution différente de celle du RFC 4941, afin de fournir des adresses stables. C'est seulement pour ces adresses stables que notre RFC recommande désormais la solution du RFC 7217.

La nouvelle règle figure donc en section 3 de notre RFC : lorsqu'une machine veut utiliser SLAAC et avoir des adresses stables, qui ne changent pas dans le temps, tant que la machine reste sur le même réseau, alors, dans ce cas et seulement dans ce cas, la méthode à utiliser est celle du RFC 7217. L'ancienne méthode (qu'on trouve par exemple dans le RFC 2464) d'ajouter le préfixe à l'adresse MAC ne doit plus être utilisée.

Notez donc bien que ce RFC ne s'adresse pas à toutes les machines IPv6. Ainsi, si vous configurez vos serveurs (qui ont clairement besoin d'une adresse stable) à la main, avec des adresses en leet comme 2001:db8::bad:dcaf, ce RFC 8064 ne vous concerne pas (puisqu'il n'y a pas de SLAAC).

Les RFC comme le RFC 4944, RFC 6282, RFC 6775 ou RFC 7428 devront donc être remplacés par des documents tenant compte de la nouvelle règles. (Cf. RFC 8065.)

Aujourd'hui, il semble que les dernières versions de Windows, MacOS, iOS et Android mettent déjà en œuvre la nouvelle règle.

Téléchargez le RFC 8064

L'article seul

RFC 8065: Privacy Considerations for IPv6 Adaptation-Layer Mechanisms

Date de publication du RFC : Février 2017
Auteur(s) du RFC : D. Thaler (Microsoft)
Pour information
Réalisé dans le cadre du groupe de travail IETF 6lo
Première rédaction de cet article le 24 février 2017

Entre la couche 3 (du modèle en couches) et la couche 2 (par exemple Ethernet) se trouve une adaptation, qui définit comment on va mettre les paquets IPv6 sur la couche sous-jacente. Certaines de ces adaptations posent des problèmes de protection de la vie privée. Ce RFC résume les problèmes existants. Chaque adaptation devra ensuite travailler à s'améliorer sur ce plan (le RFC donne des idées). L'idée est d'améliorer les normes actuelles et futures, pour mieux prendre en compte ce problème de vie privée.

Ce problème de la vie privée pour IPv6 a déjà été beaucoup discuté, notamment en raison d'un choix initial de privilégier une adaptation à Ethernet qui gardait une partie de l'adresse IPv6 constante, même quand la machine changeait de réseau. Ce problème est résolu depuis longtemps (RFC 4941) mais d'autres peuvent demeurer, surtout si la couche 2 a des contraintes qui empêchent de déployer certaines protections de la vie privée.

Les documents de référence à lire d'abord sont le RFC général sur la vie privée, RFC 6973 (sa section 5.2 est particulièrement utile ici), et, plus spécifique à IPv6, le RFC 7721. Le risque qui concerne l'adaptation est lié au mécanisme de génération des IID (identificateurs d'interface, cf. RFC 4291), puisque cet IID fait partie de l'adresse IPv6 (typiquement les 64 derniers bits) et va donc être potentiellement visible publiquement. Si l'IID est trop prévisible ou trop stable, il permettra notamment :

  • De corréler des activités du même utilisateur au cours du temps,
  • De suivre l'utilisateur à la trace s'il se déplace en gardant le même IID,
  • De balayer plus facilement un réseau à la recherche de machines à attaquer (alors que, normalement, la taille élevée de l'espace d'adressage IPv6 rend ces balayages lents et pénibles).

Un concept important est celui d'entropie, c'est-à-dire du nombre de bits dans l'IID qui sont réellement imprévisibles. Idéalement, l'entropie devrait être de 64 bits (le préfixe IPv6 ayant typiquement une longueur de 64 bits pour un réseau, cf. RFC 7421).

Voilà pourquoi le RFC 8064 déconseille de créer un IID à partir d'une adresse « couche 2 » fixe, comme l'est souvent l'adresse MAC. Il recommande au contraire la technique du RFC 7217 si on veut des adresses stables tant qu'on ne se déplace pas, et celle du RFC 4941 si on veut être vraiment difficile à tracer (au prix d'une administration réseaux plus difficile). Le RFC sur la sélection des adresses source, RFC 6724 privilégie déjà par défaut les adresses temporaires du RFC 4941.

Revenons maintenant à cette histoire d'entropie (section 2 du RFC). Combien de bits sont-ils nécessaires ? Prenons le cas le plus difficile, celui d'un balayage du réseau local, avec des paquets ICMP Echo Request ou bien avec des TCP SYN. L'entropie minimum est celle qui minimise les chances d'un attaquant de trouver une adresse qui réponde. Quel temps faudra-t-il pour avoir une chance sur deux de trouver une adresse ? (Notez que la capacité de l'attaquant à trouver des machines dépend aussi du fait qu'elles répondent ou pas. Si une machine ne répond pas aux ICMP Echo Request, et n'envoie pas de RST aux paquets TCP SYN, la tâche de l'attaquant sera plus compliquée. Cf. RFC 7288, notamment sa section 5. Même si la machine répond, un limiteur de trafic peut rendre la tâche de l'attaquant pénible. Avec la valeur par défaut d'IOS de deux réponses ICMP par seconde, il faut une année pour balayer un espace de seulement 26 bits.)

Les formules mathématiques détaillées sont dans cette section 2 du RFC. L'entropie nécessaire dépend de la taille de l'espace d'adressage mais aussi de la durée de vie du réseau. Avec 2^16 machines sur le réseau (c'est un grand réseau !) et un réseau qui fonctionne pendant 8 ans, il faudra 46 bits d'entropie pour que l'attaquant n'ait qu'une chance sur deux de trouver une machine qui réponde (avec la même limite de 2 requêtes par seconde ; sinon, il faudra davantage d'entropie).

Et combien de bits d'entropie a-t-on avec les techniques actuelles ? La section 3 donne quelques exemples : seulement 48 bits en Bluetooth (RFC 7668), 8 (oui, uniquement 256 adresses possibles, mais c'est nécessaire pour permettre la compression des en-têtes) en G.9959 (RFC 7428) et le pire, 5 bits pour NFC (RFC pas encore paru).

Ces adaptations d'IPv6 à diverses couches 2 utilisent comme identificants d'interface des adresses IEEE (comme les adresses MAC) ou bien des « adresses courtes ». Commençons par les adresses reposant sur des adresses IEEE. Dans certains cas, la carte ou la puce qui gère le réseau dispose d'une adresse EUI-48 ou EUI-64 (comme l'adresse MAC des cartes Ethernet). On peut facilement construire une adresse IPv6 avec ces adresses, en concaténant le préfixe avec cette adresse IEEE utilisée comme identificateur d'interface (IID). L'entropie dépend du caractère imprévisible de l'adresse IEEE. L'IEEE a d'ailleurs des mécanismes (pas forcément déployés dans le vrai monde) pour rendre ces adresses imprévisibles. Même dans ce cas, la corrélation temporelle reste possible, sauf si on change les adresses de temps en temps (par exemple avec macchanger).

Un argument souvent donné en faveur des adresses MAC est leur unicité, qui garantit que les adresses IPv6 seront « automatiquement » distinctes, rendant ainsi inutile la détection d'adresses dupliquées (DAD, RFC 4862, section 5.4, et RFC 4429, annexe A). Sauf que ce n'est pas vrai, les adresses MAC ne sont pas forcément uniques, en pratique et les identificateurs d'interface aléatoires sont donc préférables, pour éviter les collisions d'adresses.

En dehors des adresses allouées par un mécanismes de l'IEEE, il y a les « adresses courtes » (16 bits, utilisées par IEEE 802.15.4, cf. RFC 4944), allouées localement, et uniques seulement à l'intérieur du réseau local. Vu leur taille, elles n'offrent évidemment pas assez d'entropie. Il faut donc les étendre avant de s'en servir comme identificateur d'interface. Le RFC cite par exemple un condensat de la concaténation de l'adresse courte avec un secret partagé par toutes les machines du réseau.

On peut aussi utiliser dans le condensat le numéro de version spécifié dans la section 4.3 du RFC 6775. Ainsi, un changement de numéro de version permettra une rénumérotation automatique.

Bien, après cette analyse, les recommandations (section 4) :

  • La section Sécurité (Security Considerations) des RFC qui normalisent une adaptation à une couche 2 donnée devrait dire clairement comment on limite le balayage. Cela nécessite de préciser clairement la durée de vie des adresses, et le nombre de bits d'entropie.
  • Il faut évidemment essayer de maximiser cette entropie. Avoir des identificateurs d'adresses aléatoires est une bonne façon de le faire.
  • En tout cas, pas question de juste utiliser une adresse courte et stable avec quelques bits supplémentaires de valeur fixe et bien connue.
  • Les adresses ne devraient pas être éternelles, pour limiter la durée des corrélations temporelles.
  • Si une machine peut se déplacer d'un réseau à l'autre (ce qui est courant aujourd'hui), il faudrait que l'identifiant d'interface change, pour limiter les corrélations spatiales.

Téléchargez le RFC 8065

L'article seul

RFC 8089: The "file" URI Scheme

Date de publication du RFC : Février 2017
Auteur(s) du RFC : M. Kerwin (QUT)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF appsawg
Première rédaction de cet article le 20 février 2017

Vous connaissez le plan d'URI file:, qui indique que la ressource se trouve sur le système de fichiers local ? (Par exemple, ce fichier que je suis en train d'éditer est file:///home/stephane/Blog/RFC/8089.xml.) Ce plan avait été défini très brièvement dans le RFC 1738 (section 3.10). Tellement brièvement qu'il y manquait pas mal de choses. Ce nouveau RFC remplace cette partie du RFC 1738 et fournit cette fois une description complète du plan file:. Ce n'était pas une tâche facile car les différents systèmes de fichiers ont des syntaxes et des comportements très différents. Le RFC lui-même est très court, mais il comporte plusieurs annexes plus longues, discutant de points spécifiques parfois assez tordus.

Donc, d'abord, la définition (section 1 de notre RFC) : un fichier est un objet qui se trouve rangé dans un environnement structuré, qui fournit notamment un système de nommage, environnement qu'on nomme le système de fichiers. (Et le fichier contient des données mais ce point n'est pas crucial pour les URI et n'est donc pas mentionné.) Ensuite, les URI : ce sont les identificateurs standard du Web. Leur syntaxe générique est définie dans le RFC 3986 et ce nouveau RFC ne fait donc que spécialiser le RFC 3986. Normalement, ce RFC est parfaitement compatible avec l'ancienne syntaxe, celle du RFC 1738 mais, en pratique, comme l'ancienne définition était vraiment trop vague, il y aura forcément quelques différences. (L'annexe E donne quelques exemples de pratiques utilisées dans la nature et qui ne sont pas strictement alignées sur les nouvelles règles. Elle cite par exemple l'ajout d'un nom d'utilisateur dans l'URI. Un exemple des problèmes que ces différences posent aux navigateurs est bien expliqué dans cet article de Microsoft.)

Les URI file: ne supposent pas l'utilisation d'un protocole particulier, ni d'un type de média particulier.

Ce plan d'URI désigne des « fichiers locaux ». Un fichier local est accessible juste à partir de son nom, sans autre information (par exemple sans utiliser une adresse réseau explicite). Mais, en pratique (section 1.1), il peut être physiquement sur une autre machine, grâce à des techniques comme NFS ou SMB.

La syntaxe de ces URI figure en section 2 de notre RFC, formalisée en ABNF (RFC 5234). S'appuyant sur la syntaxe générique du RFC 3986, elle diffère légèrement de celle du RFC 1738 (l'annexe A liste les différences). Le plan file: est référencé dans le registre des plans d'URI. Je vous laisse découvrir sa grammaire dans le RFC, je donne juste des exemples qui illustrent certains points de la syntaxe :

  • Commençons par un URI banal : file:///tmp/toto.txt. Il désigne le fichier local /tmp/toto.txt de l'ordinateur sur lequel on travaille. La syntaxe du nom de fichier est celle d'Unix, même si ledit ordinateur n'utilise pas Unix. Ainsi, le fichier c:\machin\truc sur une machine Windows sera quand même file:///c:/machin/truc (il existe plein de variantes non-standard, voir l'annexe E, et l'article cité plus haut, sur les problèmes que cela pose). Sur VMS, DISK1:[CS.JANE]PAPER.PS deviendra file:///disk1/cs/jane/ (cf. annexe D).
  • Le composant après les trois barres obliques doit être un chemin absolu dans le système de fichiers de la machine. Cela a l'air simple mais la notion de « chemin absolu » ne l'est pas, et l'annexe D cite quelques surprises possibles (comme le tilde de certains shells Unix).
  • Après les deux premières barres obliques, il y a normalement un champ nommé « Autorité » (en pratique un nom de domaine), qui est optionnel. Pour les URI file:, on peut mettre dans ce champ localhost, voire n'importe quel nom qui désigne la machine locale (je ne suis pas sûr de l'intérêt que cela présente, mais c'est la norme qui, il est vrai, déconseille cet usage). Donc, l'URI cité au début aurait pu (mais ce n'est pas recommandé) être file://localhost/tmp/toto.txt. (Voir aussi la section 3 du RFC.)
  • Si on ne met pas le nom de domaine, les deux premières barres obliques sont facultatives (c'est une nouveauté de notre RFC, par rapport au RFC 1738) et file:/tmp/toto.txt est donc légal.
  • Certains systèmes de fichiers sont sensibles à la casse et il faut donc faire attention, en manipulant les URI, à ne pas changer la casse. file:///c:/machin/truc et file:///c:/Machin/TRUC sont deux URI différents même si on sait bien que, sur une machine Windows, ils désigneront le même fichier.

Que peut-on faire avec un fichier ? Plein de choses (l'ouvrir, lire les données, le détruire… La norme POSIX peut donner des idées à ce sujet.) Le plan d'URI file: ne limite pas les opérations possibles.

Évidemment, l'encodage des caractères utilisé va faire des histoires, puisqu'il varie d'une machine à l'autre. C'est parfois UTF-8, parfois un autre encodage et, parfois, le système de fichiers ne définit rien, le nom est juste une suite d'octets, qui devra être interprétée par les applications utilisées (c'est le cas d'Unix). Notre RFC (section 4) recommande bien sûr d'utiliser UTF-8, avant l'optionelle transformation pour cent (RFC 3986, section 2.5). Ainsi, le fichier /home/stéphane/café.txt aura l'URI file:/home/st%C3%A9phane/caf%C3%A9.txt, quel qu'ait été son encodage sur la machine. Au passage, j'ai essayé avec curl et file:///tmp/café.txt, file:/tmp/café.txt, file:/tmp/caf%C3%A9.txt, file://localhost/tmp/caf%C3%A9.txt et même file://mon.adresse.ip.publique/tmp/caf%C3%A9.txt marchent tous.

Et la sécurité ? Toucher aux fichiers peut évidemment avoir des tas de conséquences néfastes. Par exemple, si l'utilisateur charge le fichier file:///home/michu/foobar.html, aura-t-il la même origine (au sens de la sécurité du Web) que file:///tmp/youpi.html ? Après tout, ils viennent du même domaine (le domaine vide, donc la machine locale). Le RFC note qu'au contraire l'option la plus sûre est de considérer que chaque fichier est sa propre origine (RFC 6454).

Autre question de sécurité rigolote, les systèmes de fichiers ont en général des caractères spéciaux (comme la barre oblique ou un couple de points pour Unix). Accéder bêtement à un fichier en passant juste le nom au système de fichiers peut soulever des problèmes de sécurité (c'est évidemment encore pire si on passe ces noms à des interpréteurs comme le shell, qui rajoutent leur propre liste de caractères spéciaux). Le RFC ne spécifie pas de liste de caractères « dangereux » car tout nouveau système de fichiers peut l'agrandir. C'est aux programmeurs qui font les logiciels de faire attention, pour le système d'exploitation pour lequel ils travaillent. (Un problème du même ordre existe pour les noms de fichiers spéciaux, comme /dev/zero sur Unix ou aux et lpt sur Windows.)

Une mauvaise gestion de la sensibilité à la casse ou de l'encodage des caractères peut aussi poser des problèmes de sécurité (voir par exemple le rapport technique UAX #15 d'Unicode.)

Notons qu'il existe d'autres définitions possibles d'un URI file: (annexe C de notre RFC). Par exemple, le WhatWG maintient une liste des plans d'URI, non synchronisée avec celle « officielle », et dont l'existence a fait pas mal de remous à l'IETF, certains se demandant s'il fallait quand même publier ce RFC, au risque d'avoir des définitions contradictoires (cela a sérieusement retardé la sortie du RFC). En gros, l'IETF se concentre plutôt sur la syntaxe, et le WhatWG sur le comportement des navigateurs (rappelez-vous que les URI ne sont pas utilisés que par des navigateurs…). Il y a aussi les définitions Microsoft comme UNC ou leurs règles sur les noms de fichier.

Et, pour finir, je vous recommande cet autre article de Microsoft sur l'évolution du traitement des URI dans IE.

Téléchargez le RFC 8089

L'article seul

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

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