Introduction :
Le DNS pour Domain Name System, est un protocole ancien qui permet de retrouver une adresse d’un serveur depuis son nom. Il est pour la première fois décrit dans les RFC1034, RFC1035 et RFC2782, en 1987. Ce protocole est basé sur un serveur qui répond sur des requêtes envoyées sur le port 53 du protocole UDP (et TCP si il est impossible d’avoir de réponse sur le premier). Comme ce protocole doit s’appliquer sur le réseau internet, il utilise des adresses Unicast.
Le protocole mDNS est une version du même protocole mais en Multicast sur l’adresse 224.0.0.251 et le port 5353. Il permet à ce que tous les appareils sur un réseau puissent faire le lien entre le nom et l’adresse d’une autre machine, sans connaître le serveur DNS. En fait n’importe quelle machine voire plusieurs machines peuvent faire office de serveur DNS. La principale limitation est son usage exclusif aux réseaux locaux. Nous n’aborderons pas l’aspect sécurité du réseau dans cette article, mais nous rappelons qu’un mauvais usage des protocoles sur une adresse Multicast peut avoir des répercussions importantes sur le réseau concerné.
Accolé à ce protocole, le DNS-SD pour DNS Based Service Discovery, permet d’ajouter des informations sur les services disponibles sur chacune des machines présentes sur le réseau. Ce protocole est défini par la RFC6763 qui date de 2013. Il se base sur le protocole précédent en ajoutant un usage spécifique des champs disponibles dans le paquet de données DNS.
Le protocole DNS-SD permet ainsi de déclarer un service, tel que la présence d’un serveur d’impression ou de page web sur une machine, mais aussi de demander les machines ayant de tels services.
Présentation du protocole DNS-SD :
Un paquet DNS est défini comme suit :
C’est donc 4 listes d'enregistrements ayant tous la même structure :
- qn : pour les questions, sont les requêtes envoyées par une machine ;
- ans : pour les réponses à des questions reçues ;
- auth : pour la gestion des autorités de résolution (non abordé car cela correspond au DNS) ;
- add : pour les informations additionnelles aux réponses.
Une des forces du protocole est de ne pas redonder les informations. Ainsi chaque QName n’est copié qu’une seule fois et c’est une référence sur une partie du message, codée sur 16 bits qui est placée en lieu et place de la chaîne de caractères.
Chaque enregistrement (record) peut être de différents types :
- A : pour une adresse IPv4 ;
- PTR : pour un message nommé dans ce message (très souvent un message vide) ;
- TXT : pour une liste de chaînes de caractères (attention nous disons bien une liste de chaînes) ;
- AAAA : pour une adresse IPv6 ;
- SRV : pour les informations d’un service ;
- NSEC : pour le transfert de clé de sécurité ;
D’autres types de messages existent comme MX, SOA, CNAME, HINFO… mais ne seront pas abordés ici.
À chaque question, un répondeur doit retourner la question et une liste de réponses. A chaque réponse il est possible d’ajouter des informations additionnelles attachées à celle-ci. Un cas particulier est l’annonce, celle-ci est envoyée au démarrage pour avertir les autres utilisateurs de sa présence avant même qu’une demande soit faite. Elle ne contient donc pas de question mais seulement des réponses.
Frame 41: 236 bytes on wire (1888 bits), 236 bytes captured (1888 bits) on interface 0
Ethernet II, Src: Dell_18:c4:00 (c8:f7:50:18:c4:00), Dst: IPv4mcast_fb (01:00:5e:00:00:fb)
Internet Protocol Version 4, Src: 192.168.1.19, Dst: 224.0.0.251
User Datagram Protocol, Src Port: 5353, Dst Port: 5353
Multicast Domain Name System (response)
Transaction ID: 0x0000
Flags: 0x8400 Standard query response, No error
Questions: 0
Answer RRs: 2
Authority RRs: 0
Additional RRs: 5
Answers
_http._tcp.local: type PTR, class IN, mytest._http._tcp.local
_services._dns-sd._udp.local: type PTR, class IN, _http._tcp.local
Additional records
mytest._http._tcp.local: type TXT, class IN, cache flush
mytest._http._tcp.local: type SRV, class IN, cache flush, priority 0, weight 0, port 8080, target p-gnb-montagne.local
_http._tcp.local: type PTR, class IN, mytest._http._tcp.local
p-gnb-montagne.local: type A, class IN, cache flush, addr 192.168.1.19
p-gnb-montagne.local: type NSEC, class IN, cache flush, next domain name p-gnb-montagne.local
[Unsolicited: True]
L’annonce indique que :
- il y a un service dns-sd de nom « _http._tcp.local. ». Celui-ci a une information additionnelle dans l’emplacement #2.
- il y a un service _http nommé « mytest._http._tcp.local. ». Celui-ci a des information additionnelles dans les emplacements #0 et #1.
- l’information additionnelle #1 indique que le service « mytest._http._tcp.local. » est disponible sur le serveur « p-gnb-montagne.local. » et le port 8080
- l’information additionnelle #0 indique que le service « mytest._http._tcp.local. » est attaché à une donnée textuelle « name=toto ».
- l’information additionnelle #3 indique une adresse de résolution pour le nom « p-gnb-montagne.local. » soit 192.168.1.19.
L’intérêt est de pouvoir associer un nombre important d’informations, tout en limitant la taille du message. Ainsi l’annonce ici présente ne fait que 236 octets, alors qu’un autre système de découverte de service comme SSDP (utilisé par UPnP) qui utilise une forme de HTTP, va vite dépasser le double pour une simple annonce.
Les outils
Bonjour
C’est la référence du protocole. Développé par Apple sous licence Apache 2.0, les sources sont disponibles à l’adresse suivante :
https://opensource.apple.com/tarballs/mDNSResponder/
La version actuelle date de mai 2020, mais il est difficile de suivre les apports d'une version à une autre.
Construction
Bonjour est aussi disponible sur Linux et tout système Posix. Pour cela il suffit de télécharger l’archive, de la décompresser et de lancer « make » dans le répertoire « mDNSPosix ».
$ wget https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-1096.40.7.tar.gz
$ tar -xzf mDNSResponder-1096.40.7.tar.gz
$ cd mDNSResponder-1096.40.7/mDNSPosix
$ make
Hélas sur certains environnements il est nécessaire d’ajouter un patch :
--- a/mDNSCore/DNSCommon.h 2020-08-05 17:16:56.831707425 +0200
+++ b/mDNSCore/DNSCommon.h 2020-08-05 17:05:40.401312949 +0200
@@ -19,6 +19,7 @@
#define __DNSCOMMON_H_
#include "mDNSEmbeddedAPI.h"
+#define NULL ((void *)0)
#ifdef __cplusplus
extern "C" {
L’ensemble génère deux ensembles de binaires :
- mDNSResponderPosix, mDNSNetMonitor, mDNSClientPosix et mDNSProxyResponderPosix qui permettent un usage indépendant.
- libdns_sd.so, libnss_mdns-0.2.so, mdnsd et dns-sd qui est un démon et son(ses) client(s), pour développer un service dans une application.
Il est nécessaire de faire une installation à la main, mais tout cela fonctionne bien, l’ensemble des binaires faisant tout de même plus de 2,5Mo. L’usage de serveur « mdsnd » et de son client ne prend que 800ko et n’a aucune dépendance.
Usage
./mDNSPosix/build/prod/mDNSNetMonitor permet de suivre les services qui sont distribués sur le réseau :
$ ./mDNSPosix/build/prod/mDNSNetMonitor
17:46:56.786377 Interface 2/enp0s31f6
192.168.1.19 -R- Q: 0 Ans: 8 Auth: 0 Add: 2 Size: 284 bytes
192.168.1.19 (AN) TXT 4500 mytest._http._tcp.local. -> name=toto
192.168.1.19 (AN+) PTR 4500 _services._dns-sd._udp.local. -> _http._tcp.local.
192.168.1.19 (AN+) PTR 4500 _http._tcp.local. -> mytest._http._tcp.local.
192.168.1.19 (AN) SRV 120 mytest._http._tcp.local. -> p-gnb-montagne.local.:8080
192.168.1.19 (AN) Addr 120 p-gnb-montagne.local. -> 192.168.1.19
192.168.1.19 (AN) AAAA 120 p-gnb-montagne.local. -> 2a01:cb15:817e:2500:e10d:cf6e:d1ad:1a64
192.168.1.19 (AN) AAAA 120 p-gnb-montagne.local. -> 2a01:cb15:817e:2500:1411:e3d5:a544:38a1
192.168.1.19 (AN) AAAA 120 p-gnb-montagne.local. -> fe80::628:bb56:f1c1:2c44
192.168.1.19 (AD) NSEC 4500 mytest._http._tcp.local. -> TXT SRV
192.168.1.19 (AD) NSEC 120 p-gnb-montagne.local. -> Addr AAAA
./mDNSPosix/build/prod/mDNSResponderPosix, lui, va annoncer le nom de son hôte et un (ou plusieurs) service(s) sur le réseau.
$ ./mDNSPosix/build/prod/mDNSResponderPosix -n mytest -t _http._tcp -p 8080 -x name=toto
Le serveur « mdnsd », est livré avec un script de démarrage compatible initV. Une fois lancé, l’intégrateur peut soit utiliser la bibliothèque « libdns_sd.so » pour communiquer avec le serveur, soit utiliser simplement le client « dns-sd ».
$ sudo ./mDNSPosix/build/prod/mdnsd
$ ./Client/build/dns-sd -R mytest http.tcp local 8080 name=toto
Conclusion
Bonjour est peu utilisé sous Linux, mais il fonctionne très bien et il est bien maintenu par Apple. Il est possible de lui reprocher sa licence mais il permet une intégration dans un produit sans modification de code.
Avahi
Celui-ci est LE serveur de diffusion de Linux. Démarré par Trent Lloyd et Lennart Poettering en 2004, il est depuis sous la tutelle de freedesktop.org.
La dernière version 0.8 date de février 2020 et se trouve toujours dans le giron de Trent Lloyd :
https://github.com/lathiat/avahi
Construction
Le projet est généré par les « autotools », et peut nécessiter un important nombre de dépendances :
- D-Bus
- Glib et GTK+ 2 et 3
- Qt 4 et 5
- GDBM
- libdaemon et libevent
- mono
- Python
- expat
- systemd
Mais celles-ci peuvent être désactivées dans la majeur partie.
Pour garder le démon ainsi que la mise à jour des serveurs DNS, une configuration possible est :
$ ./configure --disable-glib --disable-gobject --disable-qt4 --disable-qt5 --disable-gtk --disable-gtk3 --disable-dbus --disable-gdbm --disable-python --disable-pygobject --disable-python-dbus --disable-mono --disable-monodoc --disable-doxygen-doc --disable-doxygen-xml --disable-doxygen-html --disable-manpages –sysconfdir=/etc --prefix=/usr
Cette configuration garde les dépendances à libdaemon, libevent et expat, et offre la possibilité de mettre à jour la résolution des adresses du réseau local, ainsi que la mise en place de services de manière statique. L’ensemble des binaires selon la plateforme n’est que d’environ 1,5 Mo. Et le paquet est compatible avec systemd et initV.
Usage
La résolution des adresses par avahi-dnsconfd est accessible sans aucune modification de l’intégrateur.
C’est la résolution des services qui devra être décrite de manière statique dans les fichiers de configuration. Un service se définit dans le répertoire « /etc/avahi/services » par un fichier xml (d’où l’usage de expat).
Voici un premier exemple pour un partage de fichiers en HTTP
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">mytest</name>
<service>
<type>_http._tcp</type>
<port>8080</port>
<txt-record>name=toto</txt-record>
</service>
</service-group>
Nous y retrouvons les informations décrites dans notre introduction :
Frame 271771: 140 bytes on wire (1120 bits), 140 bytes captured (1120 bits) on interface 0
Ethernet II, Src: Dell_18:c4:00 (c8:f7:50:18:c4:00), Dst: IPv4mcast_fb (01:00:5e:00:00:fb)
Internet Protocol Version 4, Src: 192.168.1.19, Dst: 224.0.0.251
User Datagram Protocol, Src Port: 5353, Dst Port: 5353
Multicast Domain Name System (query)
Transaction ID: 0x0000
Flags: 0x0000 Standard query
Questions: 1
Answer RRs: 0
Authority RRs: 2
Additional RRs: 0
Queries
mytest._http._tcp.local: type ANY, class IN, "QM" question
Authoritative nameservers
mytest._http._tcp.local: type SRV, class IN, priority 0, weight 0, port 8080, target p-gnb-montagne.local
mytest._http._tcp.local: type TXT, class IN
[Retransmitted request. Original request in: 271702]
[Retransmission: True]
La plupart des outils fournis par avahi dépendent de D-Bus, il n’y a donc pas de possibilité de trouver un service sur le réseau sans écrire une ligne de code.
Avahi propose des librairies permettant de décrire un service ou de trouver un service depuis son application sans passer par D-Bus. Si le fait d’écrire un annonceur de service par ce biais peut gêner le démon, il est aussi possible à notre application de découvrir un service sur le réseau. Le code d’exemple d’avahi « examples/core-browse-services.c » montre son usage pour retrouver tous les services HTTP.
Conclusion
Avahi est la référence sur Linux. Même réduit au strict minimum cet outil est tout de même lourd à embarquer sur un système. Mais la communauté qui l’entoure en fait l’un des systèmes les plus stables au point que Apple serait prêt à remplacer son outil « Bonjour » par celui-ci. Nous ne pouvons que le recommander en industrie si la place le permet.
mDns de OpenBSD
OpenBSD a sa propre implémentation de mDns mais celle-ci dépend de la librairie « util » qui est propre à ce système et incompatible en l’état avec Linux.
Les sources sont disponibles sur github : https://github.com/haesbaert/mdnsd
Tinysvcmdns
Ce produit est très léger et n’utilise que 30ko, mais il a un certain nombre d’inconvénients :
- Nécessite des patches pour créer une librairie et l’intégrer dans une application ;
- N’offre en l’état que l’exposition de services et nécessite des patches pour la découverte ;
- N’est plus maintenu par son créateur et nécessite des patches pour la correction de bugs.
Ces sources sont disponibles sur bitbucket : https://bitbucket.org/geekman/tinysvcmdns/src/default/
mais ne sont plus maintenus depuis 2013.
Un certain nombre d’utilisateurs existe tout de même. Ceux-ci mettent à jour le produit pour des corrections et des améliorations. Il est entre autre utilisé par « shairport », une implémentation du système « airplay » sous Linux et TizenRT l'OS de Samsung pour ses montres. Sa licence BSD lui permet aussi d’être intégré dans un certain nombre de solutions propriétaires ou non.
Construction
En l’état il est nécessaire de modifier le fichier testmdsnd.c pour ajouter un service.
diff -r 0e0c62e72e27 testmdnsd.c
--- a/testmdnsd.c Tue Jan 16 23:40:21 2018 +0800
+++ b/testmdnsd.c Thu Aug 06 09:40:41 2020 +0200
@@ -80,10 +80,10 @@
mdnsd_add_rr(svr, aaaa_e);
const char *txt[] = {
- "path=/mywebsite",
+ "name=toto",
NULL
};
- struct mdns_service *svc = mdnsd_register_svc(svr, "My Website",
+ struct mdns_service *svc = mdnsd_register_svc(svr, "mytest",
"_http._tcp.local", 8080, NULL, txt);
mdns_service_destroy(svc);
Usage
L’utilisation de l’application tinysvcmdns est très simple :
$ ./testmdnsd
mdnsd_start OK. press ENTER to add hostname & service
added service and hostname. press ENTER to exit
On préférera l’utiliser sous la forme d’une librairie pour l’intégrer dans une application.
Conclusion
Hormis sa taille et sa facilité d’intégration dans un produit, son usage est tout de même aléatoire. Il nécessiterait la création d’une nouvelle communauté.
L'effort de Samsung est peu récompensé, mais vous trouverez de nombreux patches importants ici:
- https://github.com/Samsung/TizenRT/pull/810/commits/2d9a3f156f1a5b551403aabef2fa9edfe1ad12bc
- https://github.com/Samsung/TizenRT/pull/810/commits/2d9a3f156f1a5b551403aabef2fa9edfe1ad12bc
- https://github.com/Samsung/TizenRT/pull/810/commits/2d9a3f156f1a5b551403aabef2fa9edfe1ad12bc
Conclusion de cet article
mDns est un protocole très intéressant dans un contexte de M2M, il permet l’intégration de machines dans un réseau hétéroclite pour un moindre coût.
Certains inconvénients subsistent :
- Il n’est pas le seul protocole d’exposition de services qui exist (SSDP, SLP, WS-Discovery et plus). Cet hétérogénéité ralentit l’adoption d’un standard.
- Il est rarement intégré aux services déjà existants. Par exemple, il permettrait un usage simplifié à un protocole comme NFS. Il est donc nécessaire de l’intégrer dans son système indépendamment du service lui-même.
Une liste importante de services est déjà enregistrée par IANA. Celle-ci est disponible sur le site de dns-sd.org:
http://www.dns-sd.org/ServiceTypes.html
Nous y notons:
- http
- https
- ipp
- ldap
- mqtt
- raop
- rtsp
L'usage des services peut rendre notre maison vraiment connectée sans être contraint à une solution propriétaire. Apple a ouvert la voie à mDNS et DNS-SD. UPnP qui était en temps très en pointe dans les objets connectés des années 2000, a perdu de son usage pour des raisons de sécurité, entre autres.