Date de publication du RFC : Février 2011
Auteur(s) du RFC : D. Thaler (Microsoft), J. Klensin, S. Cheshire (Apple)
Pour information
Première rédaction de cet article le 22 février 2011
Ce RFC de l'IAB fait
le point sur un petit problème lié aux IDN,
problème qui, semble t-il, n'avait jamais été traité en détail
avant. La norme actuelle (RFC 5890 et suivants)
est connue sous le nom d'IDNA pour IDN
in Applications car elle ne change rien au
protocole et que tout le travail doit être fait
dans l'application. Toutefois,
«application » est un terme un peu vague. Par exemple, un programme
écrit en C doit-il convertir le
U-label (forme Unicode du
nom) en A-label (forme
ASCII) avant l'appel à la fonction de
bibliothèque getaddrinfo()
? Ou bien
est-ce que getaddrinfo()
doit le faire seul ?
(Pour les impatients, la réponse, qui arrive au terme d'un long RFC,
est que l'application ne doit pas convertir en
Punycode avant d'être certaine que la résolution
de noms se fera avec le DNS, dans l'espace public.)
Les deux méthodes mènent en effet à des résultats différents dès
qu'un autre protocole que le DNS est utilisé
pour la résolution de nom. En effet, contrairement à ce qu'on lit
parfois, l'appel de getaddrinfo()
n'est pas un
appel au DNS mais aux mécanismes de résolution de nom locaux. Par
exemple, sur un système d'exploitation qui utilise la GNU
libc comme Debian, le mécanisme se
nomme NSS (pour Name Service
Switch) et le fichier
/etc/nsswitch.conf
permet de le configurer. Si
ce fichier contient une ligne comme :
hosts: files ldap dns
alors, un appel à getaddrinfo()
par une
application qui veut récupérer l'adresse IP
correspondant à un nom de domaine ne produira
pas forcément une requête DNS, le nom sera peut-être trouvé dans
/etc/hosts
ou dans LDAP
d'abord. Dans ce cas, traduire le nom en ACE
(ASCII compatible encoding, c'est-à-dire traduire palais-congrès.fr
en
xn--palais-congrs-7gb.fr
, selon le RFC 3492) serait une erreur car LDAP, par exemple,
est nativement Unicode depuis le début et n'a nul besoin de l'ACE.
La section 1 du RFC rappelle la terminologie, les textes de
référence (comme le RFC 2130) et les concepts,
notamment la notion d'encodage. Elle rappelle aussi que des opérations
aussi simples que l'égalité sont plus complexes en Unicode
qu'en ASCII. Cette même section revient sur la question des
API, résumée plus haut. La lecture du RFC 3490 pourrait faire croire à l'implémenteur d'IDN
que la situation est simple (figure 1 du RFC) avec l'application
parlant au stub resolver DNS qui parle à
l'Internet. La réalité est en fait plus riche comme le montre la
figure 2, avec un niveau d'indirection supplémentaire entre le
sous-programme appelé par l'application (par exemple
getaddrinfo()
, normalisé dans le RFC 3493) et les techniques de résolution
utilisées, le DNS mais aussi LLMNR (RFC 4795), Netbios (RFC 1001), /etc/hosts
(RFC 952),
etc.
Tiens, justement, ces API, elles prennent
des noms de domaine sous quelle forme ? UTF-8 ?
Punycode ? Autre ? La section 1.1 se penche sur
ce problème. La description de
getaddrinfo()
dit juste que le nom
(le paramètre nodename
)
est un char *
, une chaîne de caractères, sans
indiquer d'encodage. Les règles des chaînes de
caractères en C font que l'octet nul ne peut
pas en faire partie, ce qui élimine des encodages comme
UTF-16 et UTF-32. En
pratique, le nom passé peut être de l'ASCII
(c'est le cas s'il a été traité par Punycode),
de l'ISO 8859, du
ISO-2022-JP (très répandu au
Japon) ou de l'UTF-8. On
peut noter qu'il est possible de distinguer algorithmiquement certains de ces
différentes encodages avec des règles comme « Si le nom comporte un
ESC, U+001B, alors, c'est de l'ISO-2022-JP,
sinon si n'importe quel octet a un bit de poids fort à un, c'est de
l'UTF-8, sinon, si le nom commence par xn--
,
c'est du Punycode, sinon enfin c'est de l'ASCII traditionnel. » Ces
règles ne sont pas parfaites (celle-ci ne tient pas compte des ISO 8859, d'autre part un nom ASCII traditionnel peut
théoriquement commencer
par xn--
sans être un A-label)
et on peut préférer des règles plus sophistiquées comme celle exposée
dans « The
Properties and Promizes of UTF-8 ». Quant à
ISO 8859, c'est le plus ennuyeux, car il n'y a
aucun moyen fiable de reconnaître un membre du jeu ISO 8859 d'un
autre. Dans un texte long, des heuristiques sont possibles mais les
noms de domaines sont trop courts pour fournir assez d'information
pour distinguer, par exemple, ISO 8859-1 de
ISO 8859-15.
Enfin, il n'y a
pas que le getaddrinfo()
du RFC 3493, il y a d'autres sous-programmes comme, sur Windows,
GetAddrInfoW
qui accepte de l'UTF-16.
Tout cela, c'était pour expliquer que des sous-programmes de
résolution de noms comme getaddrinfo()
ne
reçoivent pas toujours assez d'information pour savoir ce qu'ils
doivent faire. L'autre moitié du problème est décrit dans la section
2 : il y a d'autres protocoles de résolution que le DNS. Par
exemple, la technologie privée d'Apple,
Bonjour, a des noms entièrement en
UTF-8 (ce qui est conforme aux recommandations
du RFC 2277). Une application qui traduirait les
noms Unicode en ACE empêcherait donc Bonjour de
trouver la machine portant ce nom. Même chose avec les autres
protocoles comme le fichier hosts
(RFC 952. Ainsi, si je mets dans mon /etc/hosts
sur Unix :
64.170.98.32 café-crème
je peux l'utiliser sans problème :
% ping -c 1 café-crème PING café-crème (64.170.98.32) 56(84) bytes of data. 64 bytes from café-crème (64.170.98.32): icmp_req=1 ttl=70 time=155 ms --- café-crème ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 155.568/155.568/155.568/0.000 ms
Traduire trop tôt est donc une méthode déconseillée : la traduction en ACE devrait se faire bien plus tard, lorsqu'on connait le protocole de résolution employé. Et cela ne peut pas se faire dans l'application à proprement parler, puisqu'elle ne connait jamais ce protocole.
D'ailleurs, puisque des protocoles comme LDAP utilisent Unicode de bout en bout, pourquoi le DNS n'en fait-il pas autant ? La section 3 décrit ce problème (voir aussi mon article « Pourquoi avoir développé IDNA au lieu d'utiliser directement l'Unicode dans le DNS ? »). Elle rappelle que, contrairement à ce que disent souvent les ignorants, le DNS est parfaitement capable, depuis le début, de faire passer autre chose que l'ASCII, a fortiori autre chose que le sous-ensemble LDH (Letters, Digits and Hyphen) de l'ASCII. Cette erreur est tellement répandue qu'il a fallu, en 1997, consacrer une part du RFC 2181 (section 11) à tordre le coup à cette idée. Le DNS peut donc gérer des octets où le huitième bit est à un et donc, par exemple, des noms en UTF-8 (il n'est donc pas en contradiction avec le RFC 2277 qui pose comme principe que UTF-8 est l'encodage Unicode de l'Internet). Il y a des raisons techniques pour ne pas utiliser UTF-16 ou UTF-32 (présence d'octets nuls qui pertuberaient les bibliothèques écrites en C pour lesquelles c'est une marque de fin de chaîne) mais UTF-8 ne pose pas de problème. En prime, UTF-8 est un sur-ensemble d'ASCII, toute chaîne ASCII est forcément une chaîne UTF-8, ce qui fait qu'une application peut tout traiter comme de l'UTF-8, même les noms existants.
Donc, le DNS ne pose pas de restrictions ASCII ou LDH. Par contre, les applications peuvent, elles, en imposer. Ainsi, pour le Web, l'ancienne norme des URI, le RFC 2396 (remplacé depuis par le RFC 3986), limitait (dans sa section 3.2.2) le nom de machine présent dans l'URL au jeu LDH (lettres, chiffres et tiret). C'est en raison de cette limitation (présente dans d'autres protocoles) que beaucoup de registres n'autorisent que ces noms à l'enregistrement.
Si le DNS n'est pas limité à l'ASCII, pourquoi ne pas faire des IDN uniquement en mettant carrément de l'UTF-8 dans les noms ? Certes, la représentation des chaînes de caractères dans le DNS ne permet pas d'indiquer l'encodage utilisé mais, après tout, le RFC 2277 dit clairement que, sur l'Internet, en l'absence de mention contraire, tout est en UTF-8. Cette méthode a effectivement été utilisée dans certaines zones, notamment privées (non visibles depuis l'Internet public). Elle marche : l'application passe de l'UTF-8 au résolveur qui la transmet aveuglément au DNS, qui sait répondre à ces requêtes. Le principal problème de cette méthode (et qui est à peine esquissé dans notre RFC 6055) est la canonicalisation. Le fait que les requêtes DNS soient insensibles à la casse ne marche pas pour l'Unicode, où les règles sont bien plus complexes (pensons au ß allemand dont la majuscule comprend deux lettres, SS, voir le RFC 5198 pour un point de vue plus large). C'est cette absence d'une canonicalisation satisfaisante (qui affecte également les autres protocoles comme LDAP), bien plus qu'une soi-disant incapacité du DNS à gérer l'Unicode, qui explique pourquoi l'actuelle norme IDN n'utilise pas UTF-8.
Parmi les autres surprises que nous réserve la résolution de noms,
la section 3 note aussi que le nom actuellement résolu n'est pas
forcément celui tapé par l'utilisateur. Il est en effet fréquent que
le nom tapé soit complété par des noms de domaines locaux. Par
exemple, sur Unix, le fichier /etc/resolv.conf
contient souvent une directive
search qui indique les noms « suffixes » à essayer (voir la
section 6 du
RFC 1536, le RFC 3397 et la section 4 du RFC 3646 pour
d'autres exemples). Si ce
fichier contient search foo.example bar.example
et qu'un utilisateur tape le nom de machine toto
,
le résolveur essaiera successivement
toto.foo.example
et
toto.bar.example
. Si
foo.example
et bar.example
utilisaient des règles différentes (par exemple que l'un de ces
domaines autorise de l'UTF-8 brut et pas l'autre), la pauvre
application qui reçoit le nom toto
n'aurait pas
de moyen de prévoir ce qui va se passer. (Ici,
toto
est en ASCII. Mais si on le remplace par
café
?)
La section 3.1 donne une longue liste d'exemples détaillés des
comportements actuels des applications. On y voit entre autres ce
qui peut se passer lorsque l'application est « trop maligne » et tente
de traduire trop tôt les noms. Par exemple, l'application reçoit le
nom crème
, le traduit en Punycode
(xn--crme-6oa
) mais
resolv.conf
contient un nom en UTF-8, mettons
café.example
et le résolveur (qui ne fait pas de
conversion en Unicode) va alors chercher la chaîne incohérente
xn--crme-6oa.café.example
!
Après toutes ses considérations, très détaillées, est-il encore
possible de donner une recommandation ? Oui, et elle tient en un
paragraphe dans la section 4 : « Une application qui va
appeler un sous-programme de résolution de noms ne doit pas convertir
le nom en Punycode si elle n'est pas absolument certaine que la
résolution se fera uniquement avec le DNS public. ». En
d'autres termes, l'application plus haut qui appelerait aveuglément, par exemple,
le sous-programme idna_to_ascii_8z()
(qui, avec
la GNU libidn, convertit du jeu de caractères
local vers Punycode) avant de faire un
getaddrinfo()
a tort.
La section 4 de notre RFC laisse une possibilité à l'application
d'essayer plusieurs méthodes, comme de tenter
getaddrinfo()
sur le nom brut, puis sur le nom
punycodé. Mais ce n'est pas obligatoire. Même recommandation pour les
traductions d'adresses IP en nom : une application « intelligente »
peut se préparer à recevoir de l'UTF-8 ou du Punycode et réagir
proprement dans les deux cas.
Dans tous les cas, une autre recommandation importante du RFC est
que les API de résolution de noms spécifient clairement l'encodage
qu'elles attendent. Sur Windows, GetAddrInfoW()
spécifie bien qu'il prend de l'UTF-16 alors que
la norme de getaddrinfo()
(le RFC 3493) n'en parle pas, faisant que la mise en
œuvre de getaddrinfo
sur Windows utilise la
page de code Windows alors que celle de
MacOS utilise UTF-8.
On peut noter que la norme IDN a été révisée récemment mais, pour les questions discutées dans ce RFC, cela n'a pas de conséquence.
Aujourd'hui, l'erreur qui consiste à traduire en ACE sans faire attention semble assez fréquente. C'est le cas par exemple d'echoping avec sa bogue #3125516.
Merci à Pascal Courtois pour sa découverte d'une bogue gênante dans ce texte (pas dans le RFC).
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)