RFC 8427: Representing DNS Messages in JSON
Stephane Bortzmeyer <[email protected]> Tue, 11 Dec 2018 15:33:52 +0100
| Newsgroups | gmane.network.dns.french |
|---|---|
| Organization | NIC France |
| Message-ID | <[email protected]> |
RFC 8427 : Representing DNS Messages in JSON
Date de publication du RFC : Juillet 2018. Première rédaction de cet article le 11 décembre 2018
https://www.bortzmeyer.org/8427.html
----------------------------
Auteur(s) du RFC: P. Hoffman (ICANN)
----------------------------
Le format des messages DNS circulant sur le réseau est un format
binaire, pas forcément évident à analyser. Pour beaucoup
d'applications, il serait sans doute préférable d'utiliser un format
normalisé et plus agréable, par exemple JSON, dont ce nouveau RFC
décrit l'utilisation pour le DNS.
Non seulement le format des messages DNS est du binaire (RFC 1035,
section 4) et non pas du texte comme par exemple pour SMTP, XMPP ou
HTTP, mais en plus il y a des pièges. Par exemple, la longueur des
sections du message est indiquée dans un champ séparé de la section, et
peut ne pas correspondre à la vraie longueur. La compression des noms
n'arrange rien. Écrire un analyseur de messages DNS est donc difficile.
Il y a un million de formats pour des données structurées mais,
aujourd'hui, le format texte le plus populaire pour ces données est
certainement JSON, normalisé dans le RFC 8259. L'utilisation de JSON
pour représenter les messages DNS (requêtes ou réponses) suit les
principes suivants (section 1.1 du RFC) :
* Tout est optionnel, on peut donc ne représenter qu'une partie d'un
message. (Des profils ultérieurs de cette spécification pourraient être
plus rigoureux, par exemple une base de "passive DNS
<https://www.bortzmeyer.org/dnsdb.html>" peut imposer que QNAME, le nom
de domaine demandé, soit présent, cf. section 6 du RFC.)
* Si l'information est présente, on peut recréer le format binaire
utilisé par le protocole DNS à partir du JSON (pas forcément bit pour
bit, surtout s'il y avait de la compression).
* Tous les noms sont représentés dans le sous-ensemble ASCII d'UTF-8,
obligeant les IDN à être en Punycode (alors que JSON permet l'UTF-8).
* Les données binaires peuvent être stockées, à condition d'être
encodées en Base16 (RFC 4648).
* Il n'y a pas de forme canonique : le même message DNS peut être
représenté par plusieurs objets JSON différents.
* Certains membres des objets JSON sont redondants et il peut donc y
avoir des incohérences (par exemple entre la longueur indiquée d'une
section ou d'un message, et sa longueur réelle, cf. la section 8 sur
les conséquences que cela a pour la sécurité). Cela est nécessaire pour
reconstituer certains messages DNS malformés. Autrement, le format
serait limité aux messages corrects.
* Le format représente des messages, pas des fichiers de zone (RFC
1035, section 5).
La section 2 du RFC donne la liste des membres (au sens JSON de
« champs d'un objet ») d'un objet DNS. Voici un exemple d'un tel objet,
une requête DNS demandant l'adresse IPv4 (code 1, souvent noté A)
d'example.com :
{ "ID": 19678, "QR": 0, "Opcode": 0,
"AA": 0, "TC": 0, "RD": 0, "RA": 0, "AD": 0, "CD": 0, "RCODE": 0,
"QDCOUNT": 1, "ANCOUNT": 0, "NSCOUNT": 0, "ARCOUNT": 0,
"QNAME": "example.com", "QTYPE": 1, "QCLASS": 1
}
Les noms des membres sont ceux utilisés dans les
RFC DNS, même s'ils
ne sont pas très parlants (RCODE au lieu
ReturnCode).
On note que les différents membres qui sont dans le DNS représentés par
des entiers le sont également ici, au lieu d'utiliser les abréviations
courantes. Ainsi, Opcode est marqué 0 et pas Q ("query"), et QTYPE
("query type") est marqué 1 et pas A (adresse IPv4). Cela permet de
représenter des valeurs inconnues, qui n'ont pas d'abréviation
textuelle, même si ça rend le résultat peu lisible si on ne connait pas
les valeurs des paramètres DNS
<https://www.iana.org/assignments/dns-parameters/dns-parameters.xml>
par coeur.
Les valeurs d'un seul bit (booléens) sont représentés par 0 ou 1, pas
par les true ou false de JSON. (J'avoue ne pas bien comprendre ce
choix.)
On note également que les longueurs des sections sont indiquées
explicitement, ici QDCOUNT ("Query Count", et ne me demandez pas à quoi
sert le D après le Q, le RFC 1035 ne l'explique pas). En JSON, cela
n'est pas obligatoire (la longueur d'un tableau, en JSON, n'est pas
spécifiée explicitement) mais, comme expliqué plus haut, cela a été
décidé pour permettre de représenter des messages DNS anormaux, par
exemple ayant un QDCOUNT de 0 et une question dans la section Question
(cf. section 8 du RFC sur les conséquences que cela peut avoir pour la
sécurité). De tels messages arrivent assez souvent dans le trafic DNS
réel vu par les serveurs connectés à l'Internet ; attaque délibérée ou
bien logiciel écrit avec les pieds ?
Et voici un exemple de réponse (QR = 1) DNS en JSON. La requête a été
un succès (RCODE = 0) :
{ "ID": 32784, "QR": 1, "AA": 1, "RCODE": 0,
"QDCOUNT": 1, "ANCOUNT": 2, "NSCOUNT": 1,
ARCOUNT": 0,
"answerRRs": [ { "NAME": "example.com.",
"TYPE": 1, "CLASS": 1,
"TTL": 3600,
"RDATAHEX": "C0000201" },
{ "NAME": "example.com.",
"TYPE": 1, "CLASS": 1,
"TTL": 3600,
"RDATAHEX": "C000AA01" } ],
"authorityRRs": [ { "NAME": "ns.example.com.",
"TYPE": 1, "CLASS": 1,
"TTL": 28800,
"RDATAHEX": "CB007181" } ]
La réponse contient un ensemble d'adresses IP
(TYPE = 1 identifie une adresse IPv4),
192.0.2.1 et 192.0.170.1. Leur
valeur est encodée en hexadécimal. C'est
moins joli que si on avait mis l'adresse IP en clair mais c'est
plus général : cela permet d'inclure immédiatement de nouveaux
types de données, au détriment de la lisibilité pour les anciens
types.
Le format de ce RFC permet aussi de décrire l'association entre une
requête et une réponse (section 3 du RFC). On les met dans un objet
JSON ayant un membre queryMessage et un responseMessage.
Si on représente une suite continue de messages DNS, faire un objet
JSON avec son accolade ouvrante et la fermante correspondante peut ne
pas être pratique. On utilise alors les séquences du RFC 7464, décrites
dans la section 4 de notre RFC.
Notre RFC spécifie également (section 7) un type MIME pour le DNS en
JSON, application/dns+json
<https://www.iana.org/assignments/media-types/application/dns+json>.
Notez qu'il ne s'agit pas de la première description du DNS en JSON.
Par exemple, j'avais décrit un format pour cela dans le brouillon
draft-bortzmeyer-dns-json
<https://datatracker.ietf.org/doc/draft-bortzmeyer-dns-json/>. Ce
format est mis en œuvre dans le "DNS Looking Glass
<https://www.bortzmeyer.org/dns-lg.html>". Mon format était plus joli,
car utilisant toujours des noms plus parlants ("Type": "AAAA" au lieu
du "QTYPE": 28. Notez toutefois que le RFC 8427 le permet également
("QTYPEname": "AAAA"). Le format plus joli ne peut de toute façon pas
être utilisé systématiquement car il ne permet pas de représenter les
types inconnus. Et mon format ne permet pas non plus de représenter les
messages malformés. (Par exemple, le ANCOUNT est toujours implicite.)
Un autre exemple de représentation des données DNS est donné par les
sondes RIPE Atlas <https://atlas.ripe.net/>. Le fichier des résultats
d'une mesure est en JSON (ce qui permet le traitement par les outils
JSON habituels comme jq
<https://labs.ripe.net/Members/stephane_bortzmeyer/processing-ripe-atlas
-results-with-jq>. Voici un exemple (si vous voulez un exemple complet,
téléchargez par exemple le résultat de la mesure #18061873
<https://atlas.ripe.net/api/v2/measurements/18061873/results/>) :
"resultset": [
{
...
"result": {
"ANCOUNT": 1,
"ARCOUNT": 0,
"ID": 38357,
"NSCOUNT": 0,
"QDCOUNT": 1,
"abuf": "ldWBgAABAAEAAAAADmN5YmVyc3RydWN0dXJlAmZyAAAcAAHADAAcAAEAAVGAABAgAUuYDcAAQQIWPv/+Jz0/",
"rt": 103.7,
"size": 63
},
},
On note que seule une petite partie des
champs de la réponse (ANCOUNT,
ID…) est exprimée en JSON, la majorité de la requête
étant dans un membre abuf qui est la
représentation binaire de la réponse.
Le service de DNS sur HTTPS de Google produit également du JSON (on le
passe à jq pour qu'il soit plus joliment affiché) :
% curl -s https://dns.google.com/resolve\?name=laquadrature.net\&type=MX | jq .
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": false,
"CD": false,
"Question": [
{
"name": "laquadrature.net.",
"type": 15
}
],
"Answer": [
{
"name": "laquadrature.net.",
"type": 15,
"TTL": 475,
"data": "5 pi.lqdn.fr."
}
]
}
en utilisant un format
spécifique à Google <https://developers.google.com/speed/public-dns/docs/dns-over-https>.
On notera que le protocole DoH, normalisé dans le RFC 8484, n'utilise *pas* JSON (et le
service de Google, contrairement à ce qu'on voit parfois écrit,
n'utilise pas DoH) mais le format binaire du DNS, utilisant le
type MIME application/dns-message. Le RFC 8484 prévoit
toutefois la possibilité de se servir de JSON pour le futur (section
4.2 du RFC 8484).