Dans cet article, nous allons traiter de l'authentification WiFi pilotée depuis D-Bus en shell. Cette authentification est historiquement gérée par l'utilitaire wpa_supplicant
, dont l'interaction se fait à travers D-Bus. L'utilitaire wpa_cli
existe déjà pour commander le démon, mais sans compter D-Bus parmi les mediums de communication supportés. L'objectif de cet article est d'explorer ce moyen de pilotage peu abordé, et d'exposer les subtilités d'utilisation de D-Bus (à travers la commande busctl
) pour y parvenir. Tout en proposant des exemples d'appels, en tant que "littérature", qui pourront être récupérés et adaptés pour d'autres utilisations.
Nous verrons comment ajouter et utiliser un réseau WiFi, avec les subtilités de description D-Bus à connaître. À travers le déroulement d'un cas d'exemple. Différents pointeurs dans le code de wpa_supplicant
seront disponibles pour quiconque souhaite approfondir sa connaissance de l'utilitaire.
Présentation des composants
wpa_supplicant
Spécifié par l'ensemble de normes sur les réseaux sans fil locaux IEEE 802.11, le WiFi est un terme générique qui regroupe plusieurs éléments distincts :
- Le firmware. Exécuté directement dans la puce WiFi, il est en charge des opérations physiques liées aux signaux
- Les pilotes noyau, englobés dans la pile de modules 802.11 (tels que
cfg80211
etnl80211
), sont en charge des interactions avec le firmware. Une interface réseau, utilisable par les applications, est également fournie. La présence de plusieurs pilotes s'explique par une volonté de séparer l'API standard, permettant l'interaction avec l'interface réseau WiFi et les différentes fonctionnalités, du code réellement bas-niveau (interaction avec le firmware). Plus d'informations disponble dans cet article wpa_supplicant
, en charge de la sécurité du WiFi (WPA, gestion de clé, etc...) côté client, par opposition àhostap
qui opère du côté du point d'accès
Schéma simplifié (source) :
Avant toute utilisation d'un réseau WiFi, par exemple pour accéder à Internet, une phase préalable d'authentification et d'association est nécessaire entre un client (la "station") et le point d'accès (AP, Access Point). Elle permet au client de négocier son insertion au sein d'un réseau WiFi, notamment concernant les aspects radio. Une fois l'authentification et l'association réalisées, le client peut interagir avec le reste des pairs, et en particulier le routeur pour un potentiel accès à Internet.
L'authentification et l'association côté client sont dévolues au noyau Linux, sous l'impulsion du démon wpa_supplicant
. Écrit en C, et actif depuis 2002. Il s'agit d'une implémentation des fonctionnalités de la norme IEEE 802.11i (WPA2). Sa configuration peut se faire via un fichier, où l'identifiant du réseau WiFi (SSID, Service Set Identifier) sur lequel se connecter est déclaré avec toutes les informations complémentaires. Le démon devient alors relativement focalisé durant toute son exécution sur le réseau WiFi désigné. D'autres méthodes de configuration sont toutefois disponibles. Citons une socket sur laquelle se connecte wpa_cli
(de type Unix localement, ou UDP sur une machine distante). Et moins connu, D-Bus, le sujet de cet article.
D-bus/busctl
D-Bus est un mécanisme d'IPC (Inter-Processus Communication). Répandu sous Linux, il s'agit d'un medium de communication local entre processus et géré par un démon. Toute application/processus peut s'y connecter pour offrir ses services, utilisables par tout pair connecté au bus. Par exemple, l'environnement graphique GNOME, NetworkManager
, Avahi
, systemd
(dont plusieurs de ses composants) et bien sûr wpa_supplicant
utilisent D-Bus. Ce qui permet de les interroger pour obtenir des informations, ou de déclencher une action.
Dans la terminologie D-Bus, qui s'inspire de la programmation orientée objet, un service désigne une application. Celle-ci peut fournir des objets, qui respectent une ou plusieurs interfaces. Chacune contenant des propriétés, des signaux (paradigme publication-souscription) et des méthodes (paradigme requête-réponse) :
Source du schéma. Plus d'informations disponible dans cet article.
La démocratisation de D-Bus est en partie expliquée par les nombreux bindings adaptés à chaque langage, tout comme la disponibilité d'outils en ligne de commande. Parmi ceux-ci, nous retrouvons le vénérable dbus-send
(première version en 2003). Mais nous nous concentrerons sur l'implémentation systemd
, busctl
, plus récente et bénéficiant de la diffusion de systemd
. Mais qui manque d'exemples quant à son utilisation conjointe avec wpa_supplicant
.
Le service D-Bus associé au démon wpa_supplicant
est fi.w1.wpa_supplicant1
, base commune à partir de laquelle nous spécifierons l'interface et l'objet pertinents au gré de nos manipulations. Celles-ci se basent sur l'API D-Bus du démon disponible à ce lien.
Enregistrement d'une interface WiFi
Au lancement du démon wpa_supplicant
, celui-ci n'a connaissance d'aucune interface réseau WiFi (sauf si une interface a été spécifiée en ligne de commande avec l'option -i
). Il est ainsi à ce stade inerte, en tout cas sur un système embarqué. Car certains outils, tel que NetworkManager
, s'occupent de porter les interfaces réseau WiFi à la connaissance de wpa_supplicant
. Cette étape peut être ainsi omise dans ce contexte.
Ajouter une interface WiFi se fait via la méthode CreateInterface
de l'interface fi.w1.wpa_supplicant1
. Supposons que nous ayons comme interface WiFi wlan0
, la commande donnera :
$ busctl call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1 CreateInterface 'a{sv}' 1 "Ifname" s "wlan0"
/fi/w1/wpa_supplicant1/Interfaces/0
Analysons-là plus en détail :
call
: permet d'appeler une méthode, qui retournera alors une réponse (paradigme requête-réponse)fi.w1.wpa_supplicant1
: le service, autrement dit l'application à contacter. La convention de nommage D-Bus est d'utiliser pour cela le nom de domaine inversé (reverse domain name)/fi/w1/wpa_supplicant1
: l'objet, formulé par un chemin (object path). Il s'agit chez la plupart des services du même nom que celui-ci, formulé en cheminfi.w1.wpa_supplicant1
: l'interface, en l'occurrence ici l'interface principale dewpa_supplicant
(lien). La convention de nommage est la même que celles des noms de servicesCreateInterface
: la méthode à appeler pour enregistrer une interface réseau -a{sv}
: type de l'argument attendu par la méthodeCreateInterface
. En l'occurrence ici un tableau d'ensembles composés d'une chaîne de caractère suivie d'un variant (n'importe quel type). Autrement dit un dictionnaire : une clé (dont le nom dépend de ce qu'attend la méthode appelée) suivie d'une valeur d'un type quelconque1
: nombre d'éléments dans ce tableau/dictionnaire"Ifname" s "wlan0"
: définition d'une cléIfname
qui contiendra une chaîne de caractères (s
), soitwlan0
L'interface est ajoutée ! Et D-Bus via busctl
nous renvoi un chemin énigmatique : il s'agit du chemin de l'objet représentatif de l'interface réseau, chemin/objet que nous utiliserons juste ensuite pour la découverte des réseaux WiFi. Cet objet sera central pour la suite de nos opérations.
Pour information, le retrait d'une interface dans wpa_supplicant
est possible avec la commande :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1 RemoveInterface o '/fi/w1/wpa_supplicant1/Interfaces/0'
Le service et l'interface restent inchangés, mais la méthode appelée devient ici RemoveInterface
. Celle-ci demande un chemin D-Bus vers un objet (o
), soit le même retourné par la méthode CreateInterface
.
Enfin, si plusieurs interfaces réseaux sont enregistrées dans le démon, et que vous désirez retrouver le chemin D-Bus de l'objet associé, un appel à la méthode GetInterface
permet cela :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1 GetInterface s 'wifi0' | jq -rc '.data[]'
/fi/w1/wpa_supplicant1/Interfaces/0
À noter ici l'utilisation de l'option -j
pour formater la sortie en JSON, réceptionnée par l'utilitaire jq
. Cela permet une sortie finale plus lisible, mais aussi facilement exploitable par un script. Toutes les commandes suivantes l'utiliseront.
Découverte des réseaux WiFi
Le démon wpa_supplicant
, à l'aide de sa nouvelle interface, est prêt pour découvrir les réseaux WiFi environnants. L'objet, retourné par la méthode CreateInterface
, possède l'interface fi.w1.wpa_supplicant1.Interface
avec dedans notamment la méthode Scan
. Ce qui donne :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface Scan 'a{sv}' 1 "Type" s "active"
$ busctl -j monitor --match 'sender=fi.w1.wpa_supplicant1,path=/fi/w1/wpa_supplicant1/Interfaces/0,type=signal,member=ScanDone
Comme pour CreateInterface
, la méthode Scan
demande un dictionnaire. La clé Type
définie à active
permet de demander un scan actif (soit l'envoi d'un sondage, probe request).
Le scan prendra quelque temps. Pourtant, le retour de la méthode Scan
a été immédiat : dans la philosophie de wpa_supplicant
, l'affichage des résultats d'un scan passe par une commande tierce (scan_results
). La même logique est reprise ici, avec une particularité : l'achèvement du scan n'est pas lors du retour de la méthode associée, mais notifié par un signal ScanDone
. D'où la seconde commande, busctl monitor
, qui écoute de façon bloquante l'arrivée du dit signal décrit par l'option --match
, et affiche chaque apparition. L'arrêt est par contre manuel, avec la combinaison de touches CTRL + C. Une variante scriptée, attendant l'apparition du signal jusqu'à 10 secondes, est proposée ici :
if ! timeout 10 sh -c "busctl -j monitor --match \
\"sender=fi.w1.wpa_supplicant1,path=/fi/w1/wpa_supplicant1/Interfaces/0,type=signal,member=ScanDone\" | \
socat EXEC: STDIO 2>/dev/null | while read -r _; do break; done"; then
>&2 printf 'Scan failed (timeout)\n'
return 1
fi
Sauf que ce signal D-Bus n'apporte pas de donnée en soi. Donc pas de liste de réseaux WiFi : wpa_supplicant
la dépose en fait dans la propriété BSSs
de l'interface fi.w1.wpa_supplicant1.Interface
. Le terme BSS (Basic Service Set) désigne une topologie réseau dont la communication sans fil entre appareils passe à travers un medium commun : le point d'accès. Chaque BSS représente en fait un point d'accès, et c'est ce terme qu'utilise wpa_supplicant
(par opposition par exemple à network ou autre). Lisons maintenant la propriété :
$ busctl -j get-property fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface BSSs | jq -r '.data[]'
/fi/w1/wpa_supplicant1/Interfaces/0/BSSs/0
/fi/w1/wpa_supplicant1/Interfaces/0/BSSs/1
/fi/w1/wpa_supplicant1/Interfaces/0/BSSs/2
...
Vous reconnaîtrez des chemins D-Bus vers des objets, un par réseau. Ces objets sont alloués au gré des réponses par les points d'accès aux sondages du démon. À noter toutefois qu'ils sont temporaires, supprimés après quelques dizaines de secondes (auquel cas un nouveau scan devra être déclenché). Alors, ne tardons pas à afficher les SSID correspondants :
for n in $(seq $(busctl -j get-property fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface BSSs | jq -rc '[ .data[] | split("/")[7] ] | "\(first) \(last)"')); do
busctl -j get-property fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0/BSSs/${n} fi.w1.wpa_supplicant1.BSS SSID | jq -cr '.data[] | "\(.)"' | awk '{printf("%c\n",$1)}'
done
Dont le vôtre, s'il est détecté. Notez l'utilisation de l'interface fi.w1.wpa_supplicant1.BSS
, qui propose bien d'autres propriétés en plus de SSID
. Cette dernière nous suffit en tout cas pour initier une connexion avec le point d'accès.
Connexion à un réseau
Tout réseau WiFi personnel ou professionnel, à l'exception de ceux des lieux grand public, sont en principe sécurisés à travers le mécanisme WPA. C'est là tout l'intérêt de wpa_supplicant
.
Les réseaux personnels s'appuient généralement sur une clé pré-partagée (WPA-PSK), qui est dérivée grâce à un mot de passe. De l'autre côté, les réseaux professionnels s'appuient sur un serveur RADIUS qui authentifie le pair entrant grâce à un certificat, nous parlons alors de WPA avec EAP. À noter que nous nous concentrerons dans cet article uniquement sur les réseaux personnels.
La première étape sera d'enregistrer le BSS dans le démon, avec au minimum son SSID et son mot de passe. Mais avec aussi les options adaptées à la version du mécanisme WPA : si les version 1 et 2 sont identiques dans le procédé de configuration, la 3ème s'appuie sur SAE (Simultaneous Authentication of Equals). SAE est un protocole d’établissement de connexion par clé, qui remplace la méthode PSK (Pre-Shared Key).
La commande d'enregistrement variera ainsi selon la version WPA qu'utilise le point d'accès :
# WPA/WP2
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface AddNetwork 'a{sv}' 3 ssid s '<ssid>' psk s '<password>' ieee80211w i 1 | jq -rc '.data[]'
/fi/w1/wpa_supplicant1/Interfaces/0/Networks/0
# WPA3
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface AddNetwork 'a{sv}' 3 ssid s '<ssid>' key_mgmt s SAE sae_password s '<password>' | jq -rc '.data[]'
/fi/w1/wpa_supplicant1/Interfaces/0/Networks/0
Un nouvel objet est retourné : /fi/w1/wpa_supplicant1/Interfaces/0/Networks/0
. Il représente le réseau WiFi ainsi enregistré, et sert surtout pour consulter les propriétés de ce dernier (voir l'interface fi.w1.wpa_supplicant1.Network
).
À ce stade, aucune connexion n'est réalisée : il s'agit simplement d'un enregistrement. La méthode AddNetwork
est l'équivalant des sous-commandes add_network
et set_network
(pour tout lecteur maniant l'utilitaire wpa_cli
). Initions enfin cette connexion :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface SelectNetwork 'o' /fi/w1/wpa_supplicant1/Interfaces/0/Networks/0
Notez l'utilisation de l'objet /fi/w1/wpa_supplicant1/Interfaces/0/Networks/0
, précédemment retourné par la méthode AddNetwork
, pour sélectionner le réseau à intégrer. Le précédent argument o
a permis de signifier à D-Bus que la valeur est un chemin D-Bus vers un objet.
Vous devriez à présent être connecté au réseau Wifi, et selon la configuration DHCP, être doté d'une adresse IP attribuée ! Mais en plus de profiter d'une connexion WiFi fraîchement établie, nous aimerons obtenir quelques propriétés la concernant. Tels que la vitesse du lien (en Mbps), le bruit (en dBm), taille du canal, fréquence (en MHz), RSSI (dBm)... Grâce à la commande :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface SignalPoll | jq -r '.data[0].data | "linkspeed:\(.linkspeed.data) Mbps noise:\(.noise.data) dBm width:\(.width.data) frequency:\(.frequency.data) MHz rssi:\(.rssi.data) dBm"'
Bien sûr, une telle commande n'est possible qu'avec une connexion établie à un réseau WiFi.
À présent, il est temps de "plier bagage" : de se déconnecter du réseau WiFi actuel. La méthode Disconnect
, sans paramètre, est à cet effet :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface Disconnect
Et pour vos raisons propres, vous voudriez supprimer l'intégralité des réseaux enregistrés, que le démon les oublie tous :
$ busctl -j call fi.w1.wpa_supplicant1 /fi/w1/wpa_supplicant1/Interfaces/0 fi.w1.wpa_supplicant1.Interface RemoveAllNetworks
Chaque réseau supprimé générera alors un signal NetworkRemoved
, qui peut être écouté avec la commande busctl monitor
ci-dessus adaptée.
Bonus
Pour les plus curieux parmi vous, ou à visée de débogage, il est tout à fait possible d'écouter les appels de méthode D-Bus vers wpa_supplicant
, ainsi que ses réponses. Cela passera comme pour l'écoute du signal ScanDone
via busctl monitor
, mais cette fois-ci avec un filtre sur les méthodes. En outre, un filtre jq
est appliqué pour améliorer la lisibilité des échanges. Jusqu'à un certain stade toutefois, la nature de certains échanges étant trop conséquente pour gérer la lisibilité sans rogner la simplicité du filtre. La commande :
$ busctl -j monitor \
--match 'destination=fi.w1.wpa_supplicant1,type=method_call' \
--match 'sender=fi.w1.wpa_supplicant1,type=method_return' | \
jq -rc '"[\(if .type == "method_call"
then "CALL] sender:\"\(.sender)\" member:\"\(.member)\""
else "RETURN] destination:\"\(.destination)\"" end
) payload/data:\(if .payload.data == [] then "<none>" else .payload end)"'" else .payload end)"'
Conclusion
J'espère avec cet article vous avoir éclairci l'utilisation de wpa_supplicant
et celle de busctl
, dont la formulation des arguments pour D-Bus peut parfois être ardue. Bien que wpa_supplicant
soit progressivement remplacé par iwd
, il reste néanmoins présent notamment dans l'embarqué. Ce qui peut vous amener à devoir interagir avec, quand bien même l'utilitaire wpa_cli
manque à l'appel. L'utilisation de D-Bus avec busctl
constituera un moyen supplémentaire d'arriver à vos objectifs. Ou simplement apprendre.