Z. Shelby (Sensinode)K. HartkeC. Bormann (Universitaet Bremen TZI)June20142014-07-02
Le protocole CoAP n'est pas destiné aux
ordinateurs, ni même aux smartphones mais principalement aux
petits engins, aux machines qui n'ont parfois qu'un
microcontrôleur 8 bits pour tout processeur,
très peu de mémoire (cf. ) et qui, en prime, sont
connectées par des liens radio lents et peu fiables (les « LowPAN » des
et ),
allant parfois à seulement quelques dizaines de kb/s. Pour de tels engins, les protocoles comme
HTTP et TCP sont trop
contraignants. CoAP est un « HTTP-like » (il reprend pas mal de termes
et de concepts
de HTTP, comme le modèle REST) mais spécialement conçu pour des applications M2M
(machine-to-machine) dans des environnements à
fortes limitations matérielles.
Le succès de HTTP et notamment de REST a
sérieusement influencé les concepteurs de CoAP. Le modèle de CoAP est celui de HTTP : protocole
requête/réponse, verbes GET,
POST, DELETE, etc, des
URI et des types de media comme sur le Web
(application/json...) Cette proximité facilitera
notamment le développement de passerelles entre le monde des objets CoAP et le Web
actuel (section 10). Une des pistes explorées
pour l'Internet des Objets (terme
essentiellement marketing mais répandu) avait été de prendre de l'HTTP
normal et de comprimer pour gagner des ressources réseau mais CoAP
suit une autre voie : définir un protocole qui est essentiellement un
sous-ensemble de HTTP. Si vous connaissez HTTP, vous ne serez pas trop
perdu avec CoAP.
CoAP tournera sur UDP (), TCP étant trop consommateur de ressources, et, si on
veut de la sécurité, on ajoutera DTLS (). CoAP fonctionne donc de manière asynchrone
(on lance la requête et on espère une réponse un jour). On pourrait
décrire CoAP comme composé de deux parties, un transport utilisant UDP
avec des extensions optionnelles si on veut de la fiabilité (accusés
de réception), et un langage des messages (requêtes et réponses en
HTTP-like).
Et les messages ? L'en-tête CoAP (section 3 du RFC) a été conçu pour être facile à
analyser par des programmes tournant sur de petites machines. Au lieu du texte lisible de HTTP, l'en-tête de CoAP
commence par une partie fixe de quatre octets, qui comprend un
Message ID de deux octets. Ce Message
ID permet de détecter les duplicatas et d'associer un accusé
de réception à un message précis. À noter qu'avec ces deux octets, on
est limité à environ 250 messages par seconde (en raison du paramètre
EXCHANGE_LIFETIME qui est à 247 secondes par
défaut). Ce n'est pas une limite bien grave : les ressources
(en énergie, en capacité réseau, etc) des machines CoAP ne leur permettront pas
d'être trop bavardes, de toute façon.
Le message contient également un champ Token
(utilisé pour mettre en correspondance les requêtes et les réponses, à
un niveau supérieur à celui des messages, qui sont, eux, identifiés
par le Message ID) et un code sur huit bits. Ce
code s'écrit sous forme d'un groupe de trois chiffres, le premier
indiquant la classe (2 : succès, 4 : erreur du client, 5 : erreur du
serveur, etc) et suivi d'un point, et les deux autres chiffres fournissant des détails. Comme vous
connaissez HTTP, vous ne serez pas étonné d'apprendre que le code 4.03 indique un
problème de permission... Un registre de ces
codes existe.
Notez bien que l'encodage, lui, est radicalement différent de celui
de HTTP. Il est en binaire et le code 4.03 sera transmis sous forme
d'un octet (10000011, avec 100 pour la classe et 11 pour le détail),
pas sous forme de la chaîne de caractères qu'utilise HTTP.
Le message contient ensuite des options, qui jouent le rôle des
en-têtes de requête de HTTP (leur liste est également
dans un registre, on y trouve des trucs
connus comme If-Match - section 5.10.8.1,
Accept - section 5.10.4 - ou
ETag - section 5.10.6). Pour économiser
quelques bits, leur encodage est assez spécial. Le numéro de l'option,
normalement sur 16 bits, n'est pas transmis tel quel mais sous forme
d'une différence avec l'option précédente. Ainsi, si on utilise les
options 4 (ETag) et 5
(If-None-Match), la première option indiquera
bien 4 mais la seconde indiquera 1... De même, les valeurs de l'option
sont violemment comprimées. Si on veut envoyer un entier, on ne doit
transmettre que le nombre d'octets nécessaire (rien si l'entier vaut
zéro, un seul octet s'il est inférieur à 255, etc).
Le transport utilisé par CoAP, UDP, ne
fournit aucune garantie de remise : un paquet UDP peut être perdu, et
UDP ne s'en occupera pas, ce sera à l'application de gérer ce
cas. CoAP fournit un service pour faciliter la vie aux applications :
on peut marquer un message CoAP comme nécessitant un accusé de
réception (type CON pour
Confirmable). Si ce type est présent dans
l'en-tête CoAP, l'expéditeur recevra un accusé de réception
(type ACK) portant le Message ID du
message reçu. Le RFC fournit l'exemple d'un capteur de
température interrogé en CoAP :
Client -> Serveur: CON [0xbc90] "GET /temperature"
Serveur -> Client: ACK [0xbc90] "2.05 Content" "22.5 °C"
Ici, le second message, la réponse du serveur, contient un accusé de
réception pour 0xbc90 (le Message ID). La requête
est un GET et la réponse a le code 2.05 (un
succès). Notez que, contrairement à HTTP, les codes sont écrits avec
un point entre le premier chiffre (la classe) et les deux autres (le
détail, cf. section 5.9). Ainsi, 4.04
a la classe 4 (erreur du client) et le détail 04 (ressource non
trouvée). Comme indiqué plus haut, les classes sont les mêmes qu'en HTTP : 2 pour un succès, 4 pour une erreur du client (qui devrait
changer sa requête avant de réessayer) et 5 pour une erreur du serveur
(réessayer à l'identique plus tard peut marcher).
Le Message ID et ces types
CON et NON (son contraire,
Non-confirmable) permettent de mettre en œuvre des
mécanismes simples de retransmission. L'émetteur envoie un message de
type CON, s'il ne reçoit pas de message de type
ACK à temps, il réessaie, avec
croissance exponentielle du délai. Le
Message-ID permet aussi de détecter les éventuels duplicatas.
On n'est pas obligé d'utiliser le type CON. Si
un message n'a pas besoin de fiabilité (par exemple les lectures
périodiques qu'envoie un capteur, que quelqu'un
écoute ou pas), il sera du type NON.
Le Message ID doit évidemment être unique,
pendant la durée de vie possible des échanges (variable
EXCHANGE_LIFETIME, quatre minutes par défaut). Il
peut être généré de manière incrémentale (l'émetteur garde un compteur
indiquant le dernier Message ID généré) mais, dans
ce cas, le RFC recommande (section 4.4) que la valeur initiale soit aléatoire, pour
limiter les possibilités d'attaque par un méchant non situé sur le
chemin des données (c'est un problème de sécurité analogue à celui des
identificateurs de fragments IP - voir l'option
--frag-id-policy de l'outil frag6 - ou celui des
numéros de séquences initiaux de TCP - voir le ).
Autre problème pour l'émetteur CoAP : quelle taille de messages ?
Les machines pour qui CoAP est conçu peuvent avoir du mal à gérer
parfaitement la fragmentation. Et puis des gros paquets ont davantage de chances d'être corrompus que des petits, sur les liens radios typiques des machines CoAP. Le RFC
recommande donc de ne pas dépasser la MTU et,
si elle est inconnue, de ne pas
dépasser 1 280 octets en IPv6. (C'est
plus compliqué pour IPv4 où le RFC recommande
576 octets et le bit DF - Don't Fragment - en
notant que, pour les usages typiques de CoAP, le chemin sera simple,
avec peu de routeurs et pas de liens à grande distance, donc peu de
chances que la fragmentation soit nécessaire.)
Comme CoAP repose sur UDP, il n'y a pas de mécanisme de contrôle de
la congestion par défaut. CoAP doit donc
s'auto-limiter : réémissions avec croissance
exponentielle des délais, et au maximum une requête en attente pour
chaque machine avec qui on communique (paramètre
NSTART). Ces règles sont pour le client. Si tous
les clients les respectent, tout ira bien. Mais un
serveur CoAP prudent doit considérer que certains clients seront
bogués ou malveillants. Les serveurs ont donc tout intérêt à avoir une forme ou
une autre de limitation de trafic.
La sémantique des requêtes et réponses CoAP figure en section 5. On
l'a dit, elle ressemble à celle de HTTP, un client qui envoie une
requête et un serveur qui transmet une réponse. L'une des plus grosses
différences avec HTTP est le caractère asynchrone de l'échange.
Les requêtes sont les classiques (section 5.8) GET,
POST, PUT et
DELETE, agissant sur une ressource donnée
(identifiée par un URI), comportant des données
optionnelles (surtout utilisées pour POST et
PUT) et utilisant les types de
média. CoAP exige que GET,
PUT et DELETE soient
idempotents (une bonne pratique mais pas
toujours respectée sur le Web). Une requête non reconnue et la
réponse sera un 4.05. La liste des requêtes possibles est dans un registre IANA.
Du fait du caractère asynchrone de l'échange, mettre en
correspondance une requête et une réponse est un peu plus compliqué
qu'en HTTP, où toutes les deux empruntaient la même connexion
TCP. Ici, la section 5.3.2 résume les règles de correspondance : une
réponse correspond à une requête si l'adresse IP source de la réponse
était l'adresse IP de destination de la requête
et que le Message ID et le
Token correspondent.
Comme HTTP, CoAP permet d'utiliser des relais. C'est d'autant plus important que les
machines CoAP sont souvent indisponibles (hibernation pour économiser
la batterie, par exemple) et un relais pourra donc répondre à la place
d'une machine injoignable. Les relais sont traités plus en détail en
section 5.7. Comme en HTTP, on pourra avoir des forward
proxies sélectionnés par les clients ou des reverse
proxies placés devant un serveur.
On l'a vu, CoAP, comme HTTP, utilise des
URI, détaillés en section 6. Ils commencent par
les plans coap: ou
coaps:. On verra ainsi, par exemple,
coap://example.com/~sensors/temp.xml ou
coaps://[2001:db8::2:1]/measure/temperature/. Attention,
tout le monde croit bien connaître les URI car ils sont massivement
utilisés pour le Web mais le RFC met en garde les programmeurs : il y
a plein de pièges dans le traitement des URI, notamment dans le
décodage des valeurs encodées en pour-cent.
Si le port n'est pas indiqué dans l'URI (ce
qui est le cas des deux exemples ci-dessus), le port par défaut est
5683 pour coap: et 5684 pour coaps:.
CoAP utilise, comme HTTP, la convention du
/.well-known/ ()
pour placer des ressources « bien connues », facilitant ainsi leur
découverte.
Justement, à propos de découverte, la section 7 détaille comment un
client CoAP va pouvoir trouver les URI des ressources
intéressantes. CoAP est conçu pour des communications de machine à
machine, où aucun humain n'est présent dans la boucle pour deviner,
faire une recherche Google, utiliser son
intuition, etc. La méthode privilégiée dans CoAP est celle du : un fichier au format normalisé situé en
/.well-known/core.
Les section 9 et 11 sont consacrées à la sécurité de CoAP. Évidemment, vu
les ressources très limitées des machines CoAP typiques, il ne faut
pas s'attendre à une grande sécurité. Par exemple, une protection par
DTLS () est
possible mais pas obligatoire, juste conseillée. En outre, les
ressources matérielles limitées des machines CoAP compliquent la tâche
du programmeur : des processeurs
très lents qui facilitent les attaques par mesure du temps écoulé,
et pas de bon générateur aléatoire. En l'absence d'un tel générateur,
on ne peut donc pas
générer des clés cryptographiques localement, il faut
le faire en usine et les enregistrer sur la machine. Et pas question
de mettre les mêmes clés à tout le monde : comme les machines CoAP seront souvent dispersées dans des
endroits ayant peu de sécurité physique (capteurs placés un peu
partout), et n'auront en général pas de protection contre les
manipulations physiques, pour des raisons de coût, un attaquant pourra
facilement ouvrir une machine et en extraire ses clés.
La complexité est l'une des ennemies principales de la sécurité. Le
RFC rappelle que le traitement des URI (bien plus compliqué qu'il n'en
a l'air) impose des analyseurs relativements complexes et donc
augmente la probabilité de bogues, pouvant mener à des failles de
sécurité. Même chose pour le format des liens du .
Question réseau, CoAP souffrira sans doute d'attaques rendues
possibles par l'usurpation d'adresses IP. Comme UDP ne nécessite pas
d'échange préalable, il est trivial d'envoyer un paquet depuis une
adresse usurpée et de le voir accepté. Cela permet des tas
d'attaques comme :
Envoyer un RST (Reset)
aux demandes de connexion vers un serveur, rendant ainsi ce serveur
inutilisable (déni de service),Envoyer une fausse réponse à une requête
GET.
Pour cette deuxième attaque, il faut pouvoir deviner le
Token utilisé. Si l'attaquant est aveugle (parce
que situé en dehors du chemin des paquets), sa tâche sera difficile si
le Token est bien aléatoire (mais ces machines
contraintes en ressources n'ont souvent pas de vrai
générateur aléatoire, au sens du ).
En pratique, il est probable que les déploiements réels de CoAP
aient lieu dans des réseaux fermés, ne contenant que des machines de
confiance, et où l'accès à l'extérieur passera par une passerelle qui
s'occupera de sécurité. Autrement, un simple
ping à destination d'une machine CoAP pourrait
suffir à vider sa batterie.
CoAP introduit aussi une autre menace, celle d'attaques par
amplification (section 11.3). Comme SNMP
et le DNS (tous les deux déjà utilisés pour ces
attaques), CoAP a des réponses plus grandes que les questions
(permettant l'amplification) et repose sur UDP
et non pas TCP (permettant l'usurpation
d'adresse IP).
La seule limite à ces attaques semble devoir être les faibles
ressources des machines CoAP, qui devraient les rendre relativement
peu intéressantes comme amplificateurs contre des serveurs
Internet. En revanche, à l'intérieur d'un réseau de machines CoAP, le
risque demeure élevé et le RFC ne propose aucune vraie solution.
CoAP est, d'une certaine façon, un vieux protocole (voir l'article
« CoAP: An Application Protocol for Billions of Tiny Internet
Nodes » de Carsten Bormann, Angelo Paolo Castellani et Zach
Shelby publié en 2012 dans dans Internet Computing, IEEE
(Volume:16 , Issue: 2) et malheureusement pas disponible
en ligne, il faut demander une copie à l'auteur). Il existe déjà
plusieurs mise en œuvre, qui ont été souvent testées dans des essais
d'interopérabilité formels. Un exemple de mise en œuvre est celle du
système Contiki.
Des exemples de requêtes CoAP décrites en détail figurent dans
l'annexe A du RFC. Grâce à Renzo Navas, voici quelques traces CoAP au format pcap :
Un simple GET du
fichier des services, /.well-known/core,Un autre GET,Un GET avec réponse,POST, PUT et DELETE.
Si tcpdump ne connait pas encore CoAP,
Wireshark, par contre, sait bien
l'analyser. Ici, une
la première requête GET du premier fichier pcap et sa réponse :
;rt="SepararateResponseTester";title="This resource will ACK anything, but never send a separate response",;rt="HelloWorldDisplayer";title="GET a friendly greeting!",;ct="21 22 23 24";rt="Ima
]]>
Merci à Laurent Toutain pour sa relecture attentive.