Au fur et à mesure des articles de ce blog, nous remontons les couches de l'infrastructure d'un système linux récent. Après avoir abordé la partie init de systemd puis avoir étudié comment les événements noyau créent des entrées dans /dev grâce à udev, il est temps de s'intéresser à l'étage suivant : la communication entre les processus via dbus.
Qu'est que dbus
d-bus est un "nouveau" système d'IPC (la première version date de 2005) qui permet de facilement passer des messages entre applications. Ce framework est de plus en plus utilisé par les daemons systèmes et par les applications linux qui veulent fournir des services avec une API standardisée. Il est donc intéressant de connaître dbus pour écrire des applications qui modifient le système lui-même de façon fiable.
Linux fournit les mécanismes traditionnels Unix d'IPC (sockets, mémoire partagée, sémaphores) mais ces mécanismes sont des primitives bas-niveau qui ne constituent pas par elles-mêmes un système de communication complet. Dbus utilise ces primitives pour construire un bus de communication permettant de communiquer facilement avec les daemons et services systèmes ou de session.
dbus apporte ainsi un certain nombre de fonctionnalités indispensables pour un bus de communication moderne.
- Modèle d'appel de fonction : les mécanismes standards ne permettent pas directement de faire des appels de fonction distants. Dbus fournit un standard de sérialisation des paramètres qui permet d'appeler de façon fiable une fonction distante.
- Sécurité des communications : le daemon dbus (chargé du routage des messages) vérifie les autorisations avant de transmettre les informations. Il ajoute également des metadonnées aux messages qui permettent à l'appelé d'avoir des informations fiables sur l'appelant afin de prendre des décisions de sécurité.
- Activation de services à la demande : le daemon dbus est configuré pour connaître certains noms fixes sur le bus et les associer à des exécutables. Il lancera l'exécutable lorsqu'un message pour ce nom fixe sera envoyé. Il est également possible (et très courant) de lier un nom sur le bus à un service systemd. Cela permet de lancer un service systemd à la demande et ainsi d'avoir un grand nombre de services disponibles sans avoir besoin de process résidents.
- Découvrabilité et introspection : il est possible d'interroger le daemon dbus pour connaître la liste des services disponibles sur le bus et chaque service peut être interrogé pour connaître les méthodes qu'il fournit ainsi que la signature de ces méthodes.
- Passage de descripteurs de fichiers : il est possible pour un service de passer un descripteur de fichier ouvert à un autre service. Cela permet de facilement créer des canaux de communication entre un daemon et une application pour passer des quantités de données importantes.
Traditionnellement, de nombreux daemons systèmes étaient fournis avec un utilitaire ligne de commande pour communiquer avec le daemon une fois celui-ci lancé. La communication se faisait via des mécanismes ad-hoc basés sur les IPC unix (typiquement via des sockets dans /var/run). Cela rendait l'utilisation de ces services systèmes très délicate, chaque daemon ayant son propre système de communication et son propre format de donnée et protocole d'échange. Beaucoup d'applications appelaient directement les utilitaires en ligne de commande depuis le code C pour ne pas avoir à réimplémenter ces protocoles. Dbus permet d'éviter à avoir à faire ce genre de choses en fournissant un système d'appel vers les daemons plus simple à utiliser depuis le code, tout en fournissant une richesse de paramètres et de fonctionnalités bien supérieure à ce que peut décrire une ligne de commande.
Si dbus est intéressant pour ses performances, c'est surtout cette standardisation du protocole de communication et le grand nombre de services standards sur le bus qui font sa valeur.
Explorer dbus
Dbus fournit une introspection très complète des services mis à disposition sur les bus. L'outil graphique d-feet permet d'explorer facilement les services disponibles. Une fois ce logiciel installé il suffit de le lancer pour pouvoir explorer le contenu de votre bus. Nous détaillerons plusieurs outils de mise au point dans la suite de cet article mais d-feet vous permettra d'explorer facilement ce que nous allons décrire ci-dessous.
Quelques services disponibles
Avant de parler du fonctionnement de dbus lui-même, regardons ce qui fait le véritable intérêt de dbus, les différents services disponibles.
Polkit
De nombreux daemons systèmes fournissent des API nécessitant des droits d'accès spécifiques pour être utilisés. Polkit fournit un mécanisme générique pour permettre à un daemon privilégié de vérifier la validité d'une requête. Le daemon va transmettre des informations sur la requête au daemon polkit, celui-ci utilise des règles (dans /etc/polkit-1/rules.d et /usr/share/polkit-1/rules.d) pour déterminer si la requête est valide. Polkit est également capable de retrouver un terminal ou une session X correspondant à l'utilisateur pour lui permettre de s'authentifier si nécessaire (via les notions de seat et session qui ne seront pas abordées dans cet article).
Polkit est donc un mécanisme de tiers de confiance facile à utiliser (des bibliothèques évitent de se préoccuper des détails de la communication avec le serveur) pour permettre à un utilisateur non-privilégié d'effectuer des actions privilégiées.
udisks
Le daemon udisks surveille les différents périphériques de stockage du système et offre une API dbus pour interagir avec eux. Ce daemon est la façon la plus pratique d'interagir avec ces périphériques, que ce soit pour détecter l'arrivée de nouveaux périphériques, pour connaître les propriétés des disques (taille, ID, point de montage etc...) ou pour les reformater. L'outil en ligne de commande udisksctl permet d'interagir avec ce daemon de façon simplifiée.
udisks utilise polkit pour les permissions. Cette combinaison permet de contrôler finement quels utilisateurs peuvent monter quels disques. La documentation complète de udisks se trouve ici.
Packagekit
Packagekit est un daemon qui fournit une API permettant d'installer des paquetages via dbus. Le gros intérêt de packagekit est de fournir une API indépendante de la distribution. Chaque distribution fournit des scripts à packagekit lui permettant de remplir son rôle quel que soit le backend utilisé.
Packagekit utilise polkit, il est donc possible d'utiliser ce mécanisme pour avoir des permissions fines sur la politique de gestion des paquetages du système (autoriser certains utilisateurs à faire des mises à jour mais pas à ajouter des paquetages, par exemple).
pkcon est un utilitaire ligne de commande permettant d'interagir avec packagekit.
NetworkManager
NetworkManager est le daemon chargé des connexions réseau. Il détecte l'ajout et la suppression de cartes réseau, ajoute des routes, envoie les requêtes DHCP etc... Nous sommes habitués à le configurer via nmcli ou l'un des applet des différents gestionnaires de fenêtre, mais toutes ces méthodes ne font qu'interagir avec le daemon via dbus. Accéder ainsi au daemon permet de vérifier facilement si une connexion est active, d'activer ou désactiver une connexion et évidemment de paramétrer les interfaces. En particulier NetworkManager permet de lister et de sélectionner les points d'accès Wifi.
Pidgin
Plus amusant, pidgin est un client IRC qui fournit une API complète sur dbus. Il est donc relativement simple de commander entièrement pidgin via des commandes dbus : envoi de messages, ajout de contacts ou de comptes, tout est possible. Notons que xchat, pour ceux qui préfèrent, fournit également un service dbus.
Il y a évidement de nombreux autres daemons sur le bus system ou utilisateur, nous verrons plus bas comment explorer tout cela, mais l'idée générale de dbus est maintenant claire. Il s'agit de fournir des services commun à tous les utilisateurs de la machine de façon standardisée. C'est dbus qui permet de changer la résolution de son écran, monter une clé USB ou changer ses paramètres réseau sans avoir besoin des privilèges root (les vieux utilisateurs de linux auront sans doute un peu de nostalgie en lisant ces lignes).
Les principes de dbus
dbus s'appuie sur un certain nombre de concepts pour implémenter ces fonctionnalités. Ces concepts sont relativement classiques dans le monde des frameworks de communication, mais passons-les tout de même rapidement en revue.
Les bus
Le concept de bus est le concept de base de dbus. Le bus est la structure permettant le passage de messages entre ses membres. Vous manipulerez en général deux bus, le bus système et le bus session.
- Le bus système : C'est le bus de la machine elle-même. La plupart des daemons y sont connectés et y fournissent les services qui sont indépendants de l'utilisateur, tels que le montage de disque, l'heure du système ou encore la résolution de nom.
- Le bus session : Chaque session ouverte sur la machine ouvre un bus session différent. Les services appartenant à l'utilisateur y sont visibles, tels que le client chat, l'application de gestion de contacts ou encore l’interrogation à distance du système GConf pour les utilisateurs de Gnome.
Il est également possible de créer ses propres bus pour des applications qui souhaiteraient communiquer entre elles sur leur propre bus. Consultez la page de manual de dbus-daemon pour en savoir plus.
Les services
Un service dbus est une application qui fournit une API sur le bus. Chaque service doit avoir un nom unique. Pour simplifier l'unicité des noms, dbus utilise la convention java des noms de domaines inversés. Ainsi, si vous envoyez un message à org.gnome.DisplayManager il sera reçu et traité par le daemon gdm de votre machine.
Si vous envoyez un message à org.freedesktop.PackageKit, dbus cherchera dans ses fichier de configuration comment lancer un service correspondant à ce nom. Il trouvera sa réponse dans le fichier /usr/share/dbus-1/system-services/org.freedesktop.PackageKit.service, reproduit ci-dessous:
[D-BUS Service] Name=org.freedesktop.PackageKit Exec=/usr/lib/packagekit/packagekitd User=root SystemdService=packagekit.service
Ce fichier dit à dbus que le service org.freedesktop.PackageKit est fourni par un service systemd appelé packagekit.service. dbus demandera donc à systemd de lancer ce service pour pouvoir lui faire passer le message.
Les clients
Un client est un programme qui utilise dbus pour communiquer avec un service. Une application en ligne de commande comme nmtui est un client dbus qui interroge org.freedesktop.NetworkManager pour obtenir des informations sur les connexions réseau actives.
Notons que de nombreux services utilisent d'autres services pour remplir leur rôle et sont donc à la fois des clients et des services.
Les objets
Chaque service présente sur le bus un ou plusieurs objets. Les objets peuvent apparaître et disparaître dynamiquement sur le bus. Ainsi, org.freedesktop.UDisks2 exporte un objet par disque et par partition sur une machine standard, soit les objets suivants : (notez la convention de nommage en nom de domaine inversés mais utilisant le séparateur de chemin unix)
- /org/freedesktop/UDisks2 : fournit des propriétés génériques communes à tous les services dbus ainsi que les événements de création d'objets par le service
- /org/freedesktop/UDisks2/Manager : permet de créer des périphériques loopback et des périphériques MD
- /org/freedesktop/UDisks2/drives/* : représentent les différents lecteurs physiques. On peut y lire de nombreuses propriétés les concernant, mais également les éteindre ou les éjecter
- /org/freedesktop/UDisks2/block_devices/* : les périphériques blocs vus par le système linux. Comme dans /dev, on retrouve ici à la fois les périphériques entiers (i.e sd*) ainsi que les partitions qui les composent (i.e sda*) . Ces objets permettent aussi bien de formater ou changer le label d'une partition que de changer le partitionnement d'un disque.
L'arborescence d'objets ci-dessus montre bien que les objets sont prévus pour être créés dynamiquement par le système. Les disques et partitions peuvent apparaître et disparaitre en fonction des périphériques branchés.
Notons également que la plupart des services exportent un unique objet ayant le même nom que le service lui-même.
Les interfaces
Les méthodes et signaux fournis par les objets sont obligatoirement regroupés en interfaces. Une interface est identifiée par un nom unique et tous les services qui fournissent une interface doivent fournir exactement les mêmes méthodes. Contrairement aux objets, les interfaces sont donc des regroupements connus et documentés. Ils sont décrits au format XML dans le répertoire /usr/share/dbus-1/interfaces
Si nous regardons le service org.freedesktop.PackageManager celui-ci n'exporte qu'un unique objet /org/freedesktop/PackageManager qui exporte cinq interfaces :
- org.freedesktop.DBus.Properties : une interface standard de dbus pour tous les services ayant des propriétés (cf plus bas). Cette interface permet de lister, lire et modifier les propriétés. Elle fournit également un signal permettant de recevoir un message lorsque la valeur d'une propriété change.
- org.freedesktop.DBus.Introspectable : une interface standard de dbus pour tous les services capables d'introspection. Une unique méthode retourne un fichier XML décrivant toute l'API de l'objet. La plupart des services utilisent des bibliothèques standard pour accéder à dbus et ces bibliothèques génèrent automatiquement les primitives d'introspection. En pratique tous les services implémentent donc l'introspection.
- org.freedesktop.DBus.Peer : une interface standard dbus pour aider à la gestion du réseau. Elle fournit une méthode Ping qui retourne immédiatement et une méthode GetMachineId qui est unique à une machine et à un boot donné. Cela permet de parler à des services à travers le réseau ou entre des machines virtuelles. Deux services ayant le même MachineId peuvent partager des SharedMemories ou des sockets locales.
- org.freedesktop.PackageKit.Offline : une interface spécifique au service PackageKit pour gérer les paquets hors-ligne.
- org.freedesktop.PackageKit : une interface spécifique au service PackageKit permettant de gérer les paquets en ligne.
La notion d'interface permet d'implémenter facilement des services indépendamment du binaire qui les exécute. La plupart des daemons implémentent les interfaces standard dbus ainsi que des interfaces spécifiques aux services qu'ils rendent.
Les méthodes, signaux et propriétés
Enfin, nous arrivons aux vrais fonctions exportées. Une méthode est un appel de fonction classique, elle peut prendre des paramètres complexes et retourner des structures et des valeurs complexes. La spécification dbus codifie strictement comment les paramètres doivent être sérialisés. Les appels entre programmes codés dans différents langages fonctionnent donc de façon naturelle pour les deux extrémités.
Un signal est un message envoyé spontanément par un service à tous les clients qui sont intéressés. Cela permet de détecter facilement les événements du système. Ainsi le service org.freedesktop.UDisks2 fournit un objet /org/freedesktop/UDisks2/ qui envoie un signal org.freedesktop.DBus.ObjetManager.InterfaceAdded lorsqu'un nouveau périphérique disque est détecté sur le système.
Une propriété est une simple valeur qui peut être lue et écrite. L'interface org.freedesktop.DBus.Properties permet de manipuler les propriétés (pour les lister, lire et écrire leurs valeurs ou pour recevoir un signal lorsqu'une propriété change). La plupart des bindings et outils cachent la nécessité d'utiliser cette API et montrent les propriétés comme des attributs lisibles directement.
Les outils d'accès à dbus
Le protocole dbus est un protocole relativement complexe qu'il vaut mieux ne pas réimplémenter soi-même. Le projet dbus met à disposition de nombreux outils pour ne pas avoir à le faire. La liste ci-dessous devrait vous permettre d'aborder sereinement dbus dans vos projets.
L'outil en ligne de commande : busctl
À tout seigneur tout honneur, commençons par l'outil ligne de commande.
Le projet dbus fournit un outil busctl pour facilement interagir avec le bus. Les quelques exemples ci-dessous vous montreront de quoi il est capable.
- busctl donne la liste des clients sur le bus
- busctl monitor surveille le bus et affiche les messages de façon lisible
- busctl capture surveille le bus, mais génère des traces au format pcap pour les relire avec wireshark
- busctl tree affiche l'ensemble des services et objets de façon arborescente
- busctl introspect <service> <objet> affiche l'API complète d'un objet. Notons que la complétion intelligente par tab fonctionne
- busctl get-property <service> <objet> <interface> <propriété> permet de récupérer la valeur d'une propriété
- busctl set-property <service> <objet> <interface> <propriété> <valeur> permet de modifier la valeur d'une propriété
- busctl call <service> <objet> <interface> <méthode> <Paramètres> appelle une méthode sur un service. Notez que la syntaxe des paramètres est assez complexe. Je recommande la page de man de busctl
d-feet
d-feet est un outil graphique d'exploration et d'appel de méthodes dbus. Il est particulièrement utile pour explorer le bus, trouver les signatures des appels de fonction et tester les API. Une fois que vous saurez les appels à faire, les autres méthodes seront sans doute plus faciles à utiliser.
libdbus
Libdbus est la bibliothèque historique d'accès à dbus. Elle a été conçue principalement pour servir de base à l'implémentation de bibliothèques de plus haut niveau et sert de couche sous-jacente à la plupart des ports vers d'autres langages de programmation. Elle est donc relativement complexe à utiliser.
gnome-dbus
La version gnome est une réimplémentation du protocole plus récente que libdbus et qui utilise l'approche objet de la bibliothèque gtk. L'approche objet permet une implémentation plus simple des différents concepts évoqués mais impose l'utilisation de gtk.
sd-dbus
sd-dbus est une implémentation plus récente du protocole dbus visant avant tout la simplicité d'utilisation par du code C. Bien que très récente, cette bibliothèque est maintenant la méthode d'accès recommandée si vous n'implémentez pas un nouveau binding.
Les autres bindings
dbus dispose de bindings pour la plupart des langages de programmation couramment utilisés. Vous en trouverez la liste complète ici.
Conclusion
dbus est maintenant la façon la plus simple d'interagir avec un système linux. L'interface programmatique est plus accessible que les anciennes méthodes ad-hoc et la plupart des services fournis par les divers daemons sont accessibles via dbus. Les outils d'exploration de dbus permettent de facilement trouver les capacités disponibles et une recherche du nom d'une interface dbus sur google permet de trouver très facilement l'API correspondante.
Connaître l’existence et les capacités de dbus devient rapidement indispensable pour le développeur embarqué car les systèmes embarqués nécessitent généralement d'interagir avec les périphériques de façon très spécifique (pour configurer le réseau wifi sans intervention humaine ou réagir à l'ajout d'une clé USB par exemple) et l'arrivée de dbus permet de faire cela de façon beaucoup plus simple que les anciennes méthodes.
Merci pour l'article ! busctl est plutôt fourni par systemd et non pas par dbus non ?