RFC 8490: DNS Stateful Operations
Stephane Bortzmeyer <[email protected]> Mon, 18 Mar 2019 12:08:15 +0100
| Newsgroups | gmane.network.dns.french |
|---|---|
| Organization | NIC France |
| Message-ID | <[email protected]> |
RFC 8490 : DNS Stateful Operations
https://www.bortzmeyer.org/8490.html
Autrefois, le DNS était toujours cité comme exemple d'un protocole sans
état. On envoie une requête, on reçoit une réponse, et le client et le
serveur oublient aussitôt qu'ils ont échangé, ils ne gardent pas de
trace de cette communication. Mais, dans certains cas, maintenir un
état sur une durée plus longue qu'un simple échange requête/réponse
peut être utile. Ce nouveau RFC propose un mécanisme pour des
*sessions* DNS, le mécanisme DSO ("DNS Stateful Operations"). Il
introduit donc une nouvelle notion dans le DNS, la persistance des
sessions.
Ne pas avoir d'état a de nombreux avantages : cela simplifie les
programmes, cela augmente les performances considérablement (pas besoin
de chercher dans une table l'état actuel d'un dialogue, le contenu de
la requête est suffisant pour donner une réponse, on peut répondre à la
vitesse de l'éclair) et cela permet de résister aux DoS, qui
réussissent souvent lorsqu'elles arrivent à épuiser le système qui
dépend d'un état. (C'est pour cela que c'est souvent une mauvaise idée
de mettre un pare-feu à état devant un serveur Internet, et c'est même
franchement absurde quand il s'agit d'un serveur DNS.) Le DNS
« habituel », tournant sur UDP et sans maintenir d'état, doit une
partie de son succès à son caractère sans état.
Mais ne pas avoir d'état a aussi des inconvénients : toutes les
options, tous les choix doivent être répétés dans chaque requête. Et
cela rend impossible de négocier des paramètres entre les deux parties,
par exemple dans le cas d'une session cryptographiquement protégée.
Bref, dans certains cas, on aimerait bien avoir une vraie session, de
durée relativement longue (plusieurs secondes, voire plusieurs
minutes). Le DNS a un mécanisme de connexion de longue durée, en
utilisant TCP (RFC 7766), et peut utiliser TLS pour sécuriser cette
communication (DoT, « "DNS over TLS" », RFC 7858) mais les requêtes à
l'intérieur de cette connexion n'en profitent pas, elles ne savent pas
qu'elles sont liées par le fait qu'elles sont dans la même connexion.
D'où ce nouveau système.
Le principe de DSO ("DNS Stateful Operations") est de permettre à une
requête DNS de créer une session, avec des paramètres communs à toute
la session (comme la durée maximale d'inactivité). La session est
balisée par des requêtes DNS utilisant l'"opcode" DSO, de numéro 6 (la
création d'un nouvel opcode
<https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-
parameters-5> est très rare). Les paramètres sont encodés en TLV (une
nouveauté dans le monde DNS ; les traditionnels "Query count" et
"Answer count", avec les sections correspondantes, ne sont pas
utilisés). La longueur du message DSO est indiquée par les deux
premiers octets du message. Les messages DSO peuvent solliciter une
réponse (même si c'est un simple accusé de réception) ou pas. Cette
sollicitation est faite par un "Message ID" différent de zéro. Si, par
contre, le "Message ID" DNS est à zéro, il s'agit d'un message DSO
unidirectionnel (retenez ce terme, il va souvent servir dans ce RFC),
qui n'attend pas de réponse. (Rappelez-vous que le "Message ID" sert à
faire correspondre requêtes et réponses DNS. Si on n'attend pas de
réponse, pas besoin d'un "Message ID". Si par contre le message est
bidirectionnel, il doit mettre un "Message ID" non nul.)
DSO ("DNS Stateful Operations", sessions - avec état, donc - pour le
DNS) ne s'applique qu'avec certains transports sous-jacents (section 4
du RFC). UDP est évidemment exclu, car il faut maintenir l'ordre des
messages, et il faut qu'il y ait une connexion à gérer. Cela peut être
TCP (RFC 1035, section 4.2.2 et RFC 7766) ou DoT (DNS sur TLS, RFC
7858). DoH (DNS sur HTTPS, RFC 8484) est par contre exclu car HTTP a
ses propres mécanimes de gestion de session. (D'autre part, la section
9.2 décrit les conséquences que cela a pour l'"anycast".)
Deux importantes utilisations de DSO sont prévues :
* Gestion de sessions et des paramètres associés : DSO va permettre de
définir des paramètres comme les durées maximales d'inactivité avant
qu'on ne coupe la connexion de transport sous-jacente. Dans ce cas, DSO
est une alternative au RFC 7828.
* Abonnements de longue durée à des services comme la découverte (RFC
6763).
La section 5 est le gros du RFC, elle décrit tous les détails du
protocole. Pour établir une session DSO, il faut :
* Établir une connexion avec un protocole comme TCP ; on est alors
connecté (on peut envoyer des messages DNS et recevoir des réponses)
mais sans session DSO,
* On envoie une demande DSO,
* Si le correspondant est d'accord, on reçoit une réponse DSO, et la
session est établie, et les paramètres comme la durée d'inactivité
maximale sont désormais contrôlés par DSO ; on peut envoyer des
messages DSO unidirectionnels (non sollicités, et ne demandant pas de
réponse),
* Si par contre le correspondant refuse DSO, on continue avec une
connexion normale.
Si on sait à l'avance que le correspondant gère DSO, on peut se
considérer comme en session dès l'établissement de la connexion. Mais,
souvent on ne sait pas ou on n'est pas sûr et il faut donc
explicitement ouvrir une session. Cela se fait avec un message DSO (un
message où l'"opcode" DNS vaut 6 ; ces "opcodes" sont décrits dans le
RFC 1035, section 4.1.1). L'acceptation prend la forme d'un message DSO
avec un "Message ID" qui correspond et un code de réponse 0 ("rcode" =
NOERROR). Si le code de réponse est autre chose que NOERROR (par
exemple 4, NOTIMP, « type de requête inconnu » ou 5, REFUSED, « je
connais peut-être DSO mais je n'ai pas envie d'en faire »), c'est que
notre correspondant ne peut pas ou ne veut pas établir une session.
Il n'y a pas de message DSO dédié à l'ouverture de session. On envoie
un message DSO de n'importe quel type (par exemple "Keepalive"). Il
peut donc arriver que le copain en face connaisse DSO mais pas ce type
particulier. Dans ce cas, il va répondre DSOTYPENI ("DSO Type Not
Implemented", code 11, une nouveauté dans le registre
<https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-
parameters-6>). La session n'est pas établie et le client doit
recommencer avec un autre type (comme "Keepalive", qui a l'avantage
d'être normalisé depuis le début et d'être obligatoire, donc il
marchera partout).
Il y a des cas plus gênants : un serveur qui couperait la connexion de
transport sous-jacente, ou bien qui ne répondrait pas aux messages DSO.
Ce cas risque de se produire si un boitier intermédiaire bogué est sur
le trajet. Il peut être alors nécessaire d'adopter des mesures de
contournement comme celles qu'utilisaient les résolveurs DNS avec les
serveurs ne gérant pas bien EDNS, mesures de contournement qui ont été
abandonnées récemment avec le "DNS Flag Day
<https://www.afnic.fr/fr/ressources/blog/1er-fevrier-2019-le-dns-va-t-il
-trembler.html>".
Si, par contre, tout se passe bien, la session DSO est établie, et des
paramètres comme le délai d'inactivité doivent désormais suivre les
règles de DSO et plus celles de normes précédentes comme le RFC 7766
(c'est pour cela que notre RFC met à jour le RFC 7766).
La section 5 détaille également le format des messages DSO. Ce sont des
messages DNS ordinaires, commençant par le "Message ID" sur deux
octets, avec l'"opcode" qui vaut DSO (code numérique 6). Les champs qui
indiquent le nombre d'enregistrements dans les différentes sections
doivent tous être mis à zéro. Les données DSO sont situées après
l'en-tête DNS standard, et sont sous forme de TLV. Le logiciel peut
donc analyser ces données même s'il ne connait pas un type DSO
spécifique. Dans une requête DSO, il y a toujours au moins un TLV, le
« TLV primaire », qui indique le type d'opérations. Les autres
éventuels TLV (« TLV additionnels ») sont là pour préciser le message.
Rappelons qu'il y a deux sortes de messages DSO, les unidirectionnels
et les autres. Les unidirectionnels ont le "Message ID" à zéro et n'ont
jamais de réponse. (Avec le "Message ID" à zéro, on ne saurait de toute
façon pas à quelle demande correspond une réponse.)
Chaque TLV comprend trois champs :
* Le type, sur deux octets (la liste des types possibles figure dans un
registre IANA
<https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-
dso-type-codes> créé par ce RFC),
* La longueur des données, sur deux octets,
* Les données.
Notez que la définition de chaque type doit préciser s'il est censé
être utilisé en TLV primaire ou additionnel. Pour une réponse, il peut
n'y avoir aucun TLV présent.
Toutes les sections « normales » d'un message DNS sont vides, y compris
la section additionnelle qu'utilise EDNS (le champ ARCOUNT doit être à
zéro). Il ne peut donc pas y avoir d'options EDNS dans un message DSO
(pour éviter la confusion qui se produirait si une option EDNS et un
message DSO donnaient des valeurs différentes au même service). Si on
veut le service équivalent à une option EDNS, il faut créer un nouveau
type DSO (section 10.3 du RFC pour les détails) et le faire enregistrer
<https://www.iana.org/assignments/dns-parameters/dns-parameters.xml#dns-
dso-type-codes>.
Combien de temps durent les sessions DSO ? D'un côté, il faut qu'elles
soient aussi longues que possible, pour amortir le coût de créer et de
maintenir des sessions sur un grand nombre de requêtes, d'un autre, il
ne faut pas gaspiller des ressorces à maintenir une session ouverte si
elle ne sert plus à rien. La section 6 du RFC discute cette question.
DSO a un délai maximal d'inactivité et, quand le délai est dépassé sans
activité, le client DSO est censé couper la connexion. (S'il ne le fait
pas, le serveur le fera, après un délai plus long.) Le client a
évidemment le droit de couper la session avant l'expiration du délai,
s'il sait qu'il n'en aura plus besoin.
Le délai maximal d'inactivité est fixé par les messages DSO de type 1.
Deux cas spéciaux : zéro indique qu'on doit fermer la connexion
immédiatement après la première requête, et OxFFFFFFFF indique que la
session peut être gardée ouverte aussi longtemps qu'on le souhaite.
DSO permet également de spécifier l'intervalle de génération des
messages "keepalives", messages envoyés périodiquement uniquement pour
que les boitiers de traduction d'adresse gardent leur état et ne
suppriment pas une correspondance adresse interne <-> adresse externe
en pensant qu'elle ne sert plus. Si on sait qu'il n'y a pas de NAT sur
le trajet, on peut mettre un intervalle très élevé. Le client peut
aussi se dire « j'ai une adresse RFC 1918, le serveur a une adresse IP
publique, il y a donc sans doute un machin NAT sur le trajet, je
demande des "keepalives" fréquents ».
Enfin, le client doit être préparé à ce que le serveur ferme la session
à sa guise, parce que le serveur estime que le client exagère (il ne
ferme pas la session alors que le délai d'inactivité est dépassé, et
qu'il n'envoie pas de requêtes), ou bien parce que le serveur va
redémarrer. Normalement, c'est le client DSO qui ferme la session mais,
dans certains cas, le serveur peut décider de le faire.
La section 7 du RFC décrit les trois TLV de base qui doivent être
présents dans toutes les mises en œuvre de DSO : "keepalive", délai
avant de réessayer, et remplissage. La section 8.2 indique dans quels
cas ils peuvent être utilisés par le client ou par le serveur.
Le TLV "keepalive" contrôle l'envoi de messages servant uniquement à
indiquer que la session est toujours ouverte, afin notamment de
rassurer les routeurs NAT. Ce même TLV sert également à indiquer le
délai d'inactivité maximal. Comme ce type de TLV est obligatoire, c'est
un bon candidat pour le message initial d'ouverture de session (il n'y
a pas de message particulier pour cette ouverture : on envoie juste un
message ordinaire). Il a le type 1 et comprend deux champs de données,
le délai maximal d'inactivité, en millisecondes, sur quatre octets, et
l'intervalle d'émission des "keepalives", également en millisecondes,
et sur quatre octets. Il peut être utilisé comme TLV primaire, et il
requiert une réponse, le "Message ID" doit donc être différent de zéro.
La valeur du délai maximal d'inactivité émise par le client est un
souhait, la valeur à utiliser est celle qui figure dans la réponse du
serveur. Si le client ne la respecte pas par la suite, le serveur aura
le droit de fermer la session. Notez qu'EDNS avait déjà un mécanisme
équivalent, pour définir une durée d'inactivité maximale dans les
connexions TCP, normalisé dans le RFC 7828. Mais les limites d'EDNS,
comme le fait que les options EDNS ne s'appliquent normalement qu'au
message en cours, rendent cette solution peu satisfaisante. Cet ancien
mécanisme ne doit donc pas être utilisé avec DSO, qui dispose, d'un
autre système, celui utilisant les valeurs spécifiées par un message
portant le TLV "Keepalive".
Une fois la durée d'émission des "keepalives" fixée, les messages de
"keepalive" seront des messages unidirectionnels (pas de réponse) et
donc envoyés avec un "Message ID" nul.
Deuxième type de TLV obligatoire, le délai avant de réessayer de se
connecter, qui a le code 2. C'est un message unidirectionnel, envoyé
par le serveur pour indiquer qu'il va couper et qu'il ne faut pas
réessayer avant la durée indiquée en valeur du TLV.
Et enfin, le troisième type (code 3) qui doit être présent dans toute
mise en œuvre de DSO est le remplissage. Le but est d'améliorer la
protection de la vie privée en insérant des données bidon dans les
messages DNS, pour rendre plus difficile l'analyse des données
chiffrées. Il n'a évidemment de sens que si la session sous-jacente est
chiffrée, par exemple avec le RFC 7858. Pour la longueur du remplissage
à choisir, voir le RFC 8467.
Comme toujours sur l'Internet, une grande partie des problèmes
opérationnels viendront des "middleboxes". Le RFC rappelle à juste
titre que la meilleure solution serait de ne pas avoir de "middleboxes"
mais, comme c'est un idéal lointain, en attendant, il faut se pencher
sur ce que font ces fichus boitiers intermédiaires, qui se permettent
parfois d'intercepter automatiquement le trafic DNS et de le modifier.
Si le boitier gère DSO et répond correctement aux spécifications de ce
RFC, tout va bien. Si le boitier ne comprend pas DSO et renvoie un
NOTIMP ou équivalent, cela empêche d'utiliser DSO mais, au moins, cela
ne viole pas la norme : le client réagira comme si le serveur ne
connait pas DSO. Si le boitier ne connait pas le DNS, et n'essaie pas
de le comprendre, ça devrait marcher si, bien sûr, il établit bien une
connexion et une seule pour chaque connexion entrante (c'est ce que
fait un routeur NAT qui ne regarde pas les couches supérieures).
Dès que le boitier ne respecte pas ces règles, on peut prévoir des
ennuis, et qui seront très difficiles à déboguer. Par exemple si un
répartiteur de charge DNS reçoit des connexions TCP, les ouvre, et
envoie chaque requête DNS qu'elles contenaient à un serveur différent,
le client DSO va certainement souffrir. Il croira avoir une session
alors qu'il n'en est rien.
Autre problème pratique qui se posera peut-être : les optimisations de
TCP. Deux d'entre elles ont des chances sérieuses de créer des ennuis,
l'algorithme de Nagle et les accusés de réception retardés (on attend
un peu de voir si un autre segment arrive, pour pouvoir accuser
réception des deux avec un seul paquet, RFC 1122, section 4.2.3.2).
Pour les messages DSO bidirectionnels, pas de problème. Pour les
unidirectionnels, en revanche, le retard de l'accusé de réception
pourra atteindre 200 millisecondes, ce qui est énorme dans un centre de
données typique, avec des liens qui peuvent débiter plus d'un gibabit
par seconde. L'algorithme de Nagle fera qu'on n'enverra pas de données
tout de suite, attendant s'il n'y a pas quelque chose à transmettre et,
avec l'accusé de réception retardé, la combinaison des deux retardera
sérieusement l'envoi
<http://www.stuartcheshire.org/papers/nagledelayedack/>.
Débrayer l'algorithme de Nagle, ou bien les accusés de réception
retardés, résoudrait le problème mais ferait perdre d'utiles
optimisations. En fait, la seule solution propre serait que les API
permettent aux applications de dire à TCP « il n'y aura pas de réponse
à ce message, envoie l'accusé de réception tout de suite ».
Enfin, un petit mot sur la sécurité pour finir. DSO nécessite des
connexions permanentes et, potentiellement, cela peut consommer pas mal
de ressources sur le serveur. Pour se protéger, le serveur a donc
parfaitement le droit de limiter le nombre de connexions maximal, et de
fermer des sessions quand ça lui chante.
Toujours sur la sécurité, DSO permet des établissements de connexion
sans aller-retour, avec TCP "Fast Open" (RFC 7413) et TLS 1.3 (RFC
8446). C'est très rapide, c'est très bien mais les données envoyées
avec le premier paquet ("early data") ne sont pas forcément bien
sécurisées et la définition de chaque type de TLV doit donc indiquer
s'il est sûr ou pas de l'utiliser dans le premier paquet.
Il semble qu'à l'heure actuelle, il n'y a pas encore de mise en œuvre
de cette technique DSO.