Linux Embedded

Le blog des technologies libres et embarquées

Présentation des Wireless Daemon sous Linux

https://www.pexels.com/fr-fr/photo/a-l-interieur-chat-d-argent-citadin-commerce-1755792/

Introduction

À en croire les forums de différentes distributions GNU/Linux ; le Wi-Fi sous Linux est lent, difficile à configurer, la documentation est pauvre et globalement il est bien trop compliqué à utiliser. Si une si grande partie de la communauté tend à avoir des difficultés avec le Wi-Fi c’est que ces reproches sont en partie fondés. Une des cibles de ceux-ci est wpa_supplicant : le logiciel userspace historique permettant de connecter son PC en temps que client à un Access Point Wi-Fi.

Conscient des ces lacunes, Intel a pris la décision de développer une nouvelle solution nommée iwd (pour iNet wireless daemon) qui viendrait remplacer et pallier les lacunes de wpa_supplicant.

Le but de cet article est en premier lieu de démystifier wpa_supplicant. Nous y évoquerons son principe de fonctionnement, sa compilation et sa configuration. De la même manière iwd sera également présenté, il sera alors l’occasion de faire un comparatif des forces en présence.

Vous l’aurez compris cet article est centré autour de logiciels userspace, si vous voulez en savoir plus sur la stack Wi-Fi côté kernel je vous conseille l’excellent article de Geoffrey Le Gourrierec : https://www.linuxembedded.fr/2020/05/emulating-wlan-in-linux-part-i-the-802-11-stack/

Wpa_supplicant

Qu’est-ce que wpa_supplicant ? un peu d’histoire…

Dès le début des années 2000 le mécanisme de sécurité d’origine des normes IEEE 802.11, l’algorithme WEP (Wired Equivalent Privacy) s’est avéré insuffisant pour la plupart réseaux. La Wi-Fi alliance a donc été contrainte de mettre à jour ses normes pour corriger les failles de sécurité existantes.

Cela a conduit à la création d’un nouveau mécanisme de sécurisation des réseaux Wi-Fi : le Wi-Fi Protected Access (WPA). WPA utilise un protocole de communication appelé TKIP (Temporal Key Integrity Protocol) dont l’idée est de chiffrer chaque paquet avec une nouvelle clé plutôt qu’avec une clé constante ou partiellement constante.

Les clés peuvent être gérées à l’aide de deux mécanismes différents. WPA peut soit utiliser :

  • un serveur d’authentification externe comme RADIUS (WPA-Enterprise) et utiliser EAP comme protocole d’authentification
  • des clés pré-partagées sans avoir besoin de les serveurs externes (WPA-PSK)

Les deux mécanismes généreront une clé de session à usage unique pour l’Authenticator (AP) et le Supplicant (client).

C’est à la suite de ces évolutions de normes que wpa_supplicant a été développé. En résumé celui-ci est une implémentation du composant « WPA Supplicant », c’est-à-dire la partie qui s’exécute dans les stations clientes. Il implémente la négociation de clé avec un authentificateur (AP), le roaming et l’authentification/association du pilote wlan.

Compilation, intégration et utilisation

Compilation

Les normes Wi-Fi ne cessent d’évoluer et de la même manière le code de wpa_supplicant n’a cessé de s’étoffer. Avec environ 400 000 lignes de code on peut considérer wpa_supplicant comme un projet imposant au premier abord.

Heureusement tout comme le kernel la compilation de wpa_supplicant est modulaire et l’utilisateur pourra choisir de compiler uniquement les composants qui lui semblent nécessaires/supportés par son driver. Cela passe par un fichier de configuration « .config » qui permet de customiser wpa_supplicant de façon assez fine : des options de debug, support de certains cipher ou non, support de dbus, support de certaines bandes de fréquences ou non etc..

Note : attention, même si un fichier .config existe, Kbuild/Kconfig n’est pas utilisé par wpa_supplicant ce qui peut sembler assez déroutant. La description des options n’est donc pas faite dans un fichier kconfig mais directement dans le .config. Celles-ci sont alors utilisées par un gros Makefile (non généré par un système de construction de Makefile comme autotools/cmake).

La compilation de wpa_supplicant mène à la production de plusieurs binaires dont :

  • wpa_supplicant : le démon
  • wpa_cli : un exemple de client permettant de piloter wpa_supplicant via dbus ou un interface de contrôle (socket unix)
  • libwpa_client.so : une librairie partagée permettant d’écrire sa propre version de client pour piloter wpa_supplicant

Vous pouvez le cloner ici :

git clone git://w1.fi/srv/git/hostap.git

Vous l’aurez sans doute remarqué : le répertoire ne s’appelle pas « wpa_supplicant » mais hostap. Cela est certainement lié au driver Linux « hostap » maintenant obsolète et développé par Jouni Malinen également développeur de wpa_supplicant. Le répertoire contient aussi les sources d’hostapd, le démon permettant d’utiliser une interface wlan en Access Point Wi-Fi. Une bonne partie du code est commun avec wpa_supplicant et l’utilisation est semblable.

Intégration

Wpa_supplicant et bien évidemment disponible sur Buildroot et Yocto mais il est à noter que les recettes Yocto de wpa_supplicant / hostapd sont séparées dans Poky et meta-openembedded. Celles-ci permettent d’intégrer directement un fichier de configuration de service systemd si votre distribution en dépend (wpa_supplicant intègre également des scripts de démarrage pour d’autres systèmes d’init comme sysVinit ou openRC).

Note : Avec le système de .config de wpa_supplicant / hostpad il n’est pas possible de choisir quelles options de wpa_supplicant compiler en passant une option de compilation au Makefile. Il faut nécessairement aller modifier/remplacer le .config dans le répertoire où les sources sont décompressées.

Utilisation

Wpa_supplicant est un démon qui peut fonctionner sans action du userspace à condition qu’un fichier de configuration soit fourni. Il peut également être piloté par un client, dans ce cas il utilise une boucle événementielle pour recevoir /envoyer des signaux au userspace via dbus ou un socket unix. Cette boucle permet également de recevoir des notifications du kernel (via une interface générique).

Dans le cas des distributions utilisant NetworkManager wpa_supplicant utilise dbus comme le montre la configuration du service systemd :

❰jebro❙~❱✔≻ systemctl cat wpa_supplicant
# /lib/systemd/system/wpa_supplicant.service
[Unit]
Description=WPA supplicant
Before=network.target
After=dbus.service
Wants=network.target

[Service]
Type=dbus
BusName=fi.w1.wpa_supplicant1
ExecStart=/sbin/wpa_supplicant -u -s -O /run/wpa_supplicant

[Install]
WantedBy=multi-user.target
Alias=dbus-fi.w1.wpa_supplicant1.service

Dans le cas d’une distribution qui n’utilise pas dbus, wpa_supplicant peut alors utiliser un socket unix standard et doit être lancé à minima avec les options suivantes -C <path> sur le filesystem de l’interface de contrôle, -i <interface> sur laquelle écouter. Par exemple :

sudo wpa_supplicant -C /var/run/wpa_supplicant -i wlan0

Wpa_supplicant peut être également lancé par un utilisateur non privilégié avec les bonnes capabilities appliquées :

sudo setcap cap_net_raw,cap_net_admin+ep wpa_supplicant

Les opérations du démon wpa_supplicant sont contrôlables depuis cette interface de contrôle qui peut être utilisée par des programmes externes. Wpa_cli est un exemple de client qui utilise cette interface de contrôle pour configurer wpa_supplicant.

Il peut être appelé en mode interactif ou non, pour déclencher un scan par exemple :

sudo wpa_cli -i wlan0 scan

Note : Le sudo est uniquement nécessaire ici pour l’accès au socket si wpa_supplicant est lancé par l’utilisateur root. Wpa_cli n’a besoin sinon, d’aucune capability particulière.

Puis récupérer le résultat :

sudo wpa_cli -i wlan0 scan_results
...
30:7c:b3:5a:c4:q4	5560	-46	[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]	Livebox-titi
30:7c:b3:5a:c4:q5	2437	-42	[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]	Livebox-titi
3c:e1:a1:0a:91:4c	2412	-62	[WPA2-PSK-CCMP][ESS][UTF-8]	test
58:90:43:e8:12:c7	5300	-79	[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS]	Livebox-toto
...

Puis pour configurer et se connecter à un Access Point scanné :

sudo wpa_cli -i wlan0 add_network
sudo wpa_cli -i wlan0 set_network 0 ssid \"Livebox- titi\"
sudo wpa_cli -i wlan0 set_network 0 psk \"testtest\"
sudo wpa_cli -i wlan0 enable_network 0

Ce qui doit provoquer la réception d’un événement informant le succès de la connexion :

<3>CTRL-EVENT-CONNECTED - Connection to 30:7c:b3:5a:c4:q4 completed [id=0 id_str=]

Mini-TP : création d’un « dummy client »

Une librairie partagée peut être compilée pour le développement d’un client custom. C’est ce que je vous propose avec ce mini TP, création d’un « Dummy » client qui va demander à wpa_supplicant de scanner en boucle et qui va lister tous les événements envoyés par wpa_supplicant au user space. Le code de wpa_cli fait figure d’exemple au développement d’un client custom.

On clone pour cela le repo et compile la librairie partagée :

git clone git://w1.fi/srv/git/hostap.git
cd hostap/wpa_supplicant/ && make libwpa_client.so
mkdir ~/test_wpa
cp libwpa_client.so ../src/common/wpa_ctrl.h ~/test_wpa

Les principales étapes de création d’un client de wpa_supplicant sont les suivantes :

1 ) Ouverture de l’interface de contrôle :

La commande wpa_ctrl_open() crée une structure wpa_ctrl qui permet d’ouvrir une connexion à l’interface de contrôle afin d’envoyer et de recevoir le résultat des commandes.

Ici on créé une première instance que nous allons utiliser pour que wpa_supplicant demande au driver de lancer un scan.

std::string itfName = "/var/run/wpa_supplicant/wlan0";

struct wpa_ctrl * commandCtrl = nullptr;
// Open a control interface to wpa_supplicant/hostapd used to send commands.
commandCtrl = wpa_ctrl_open(itfName.c_str());
if(commandCtrl == nullptr)
{
    return 1;
}

Wpa_supplicant envoie régulièrement des notifications via l’interface de contrôle pour prévenir le userspace de différents types d’événements comme par exemple le lancement d’un scan, la connexion et la déconnexon à un Access Point.

Même si ce n’est pas obligatoire, il est donc plus simple d’ouvrir une deuxième connexion utilisée pour récupérer les notifications de wpa_supplicant. Cela permet d’éviter que l’attente d’une réponse par la première connexion soit interrompue par une notification.

struct wpa_ctrl * eventCtrl = nullptr;
//Open a control interface to wpa_supplicant/hostapd used to receive notif.
eventCtrl = wpa_ctrl_open(itfName.c_str());
if(eventCtrl == nullptr)
{
    return 1;
}

2) Fonction d’envoi des requêtes de scan à wpa_supplicant :

La fonction qui permet d’envoyer des requêtes à wpa_supplicant est wpa_ctrl_request, c’est cette commande qui permet notamment d’ajouter un nouveau réseau et de le configurer en ajoutant par exemple son ssid, bssid, psk, type de sécurité, etc. Ici nous l’utilisons pour envoyer un scan.

char replyChar[256];
size_t replyLen = 255;
std::string scanCommand = "SCAN";
int ret = wpa_ctrl_request(commandCtrl, scanCommand.c_str(), scanCommand.length(), replyChar, &replyLen, nullptr);
if (ret != 0)
{
      std::cout << "Sending scan failed" << "\n";
      continue;
}

3) Fonction d’écoute des événements reçus :

Ici on vérifie si des messages reçus sont en attente ou non avec wpa_ctrl_pending, si c’est le cas la commande wpa_ctrl_recv permet de récupérer le message.

// Register as an event monitor for the control interface.
if(wpa_ctrl_attach(eventCtrl) != 0)
{
    return false;
}

while(1)
{
   //check whether there are any pending control interface message available to be received 
    if(wpa_ctrl_pending(eventCtrl) == 0)
    {
        std::this_thread::sleep_for (std::chrono::seconds(1));
        continue;
    }

    char reply[256];
    size_t replyLen = 255;
    // Receive a pending control interface message. 
    if(wpa_ctrl_recv(eventCtrl, reply, &replyLen) != 0)
    {
        std::this_thread::sleep_for (std::chrono::seconds(1));
        continue;
     }

     std::string replyStr(reply, replyLen);
     std::cout << "Event received " << replyStr << "\n";
     std::this_thread::sleep_for (std::chrono::seconds(1));
}

Après un peu de mise en forme, on peut alors compiler et tester notre programme :

On remarque que lorsqu’un scan envoyé, un événement informant la bonne réception de la demande est reçue : CTRL-EVENT-SCAN-STARTED.

Le bssid des réseaux scannés est alors retourné via l’événement CTRL-EVENT-BSS-ADDED. En quelques centaines de lignes de code il est alors possible de réécrire votre propre version de wpa_cli qui va se charger de configurer wpa_supplicant.

Limites

Même si il est relativement aisé de créer son propre client, wpa_supplicant est un logiciel qui souffre d’un manque de documentation. Les fonctions les plus utilisées que je vous ai décrites dans le mini TP sont documentées sur la « Developers’s documentation for wpa_supplicant and hostapd » https://w1.fi/wpa_supplicant/devel/ctrl_iface_page.html . Néanmoins certaines fonctions moins courantes ou certains événements, ou paramètres sont partiellement – voire non – documentés.

Une autre limite de wpa_supplicant est que sans frontend de type Network Manager ou Connman la configuration peut paraître complexe pour un utilisateur encore non initié. Certains forums présentent heureusement un guide partiel d’utilisation de wpa_cli.

La limite la plus importante de wpa_supplicant est sa dépendance avec des logiciels tiers. Dans le cadre d’une distribution desktop classique l’UI de Network Manager va se charger de récupérer la configuration des réseaux connus, va appeler wpa_supplicant qui va appeler des librairies de cryptographies comme GnuTls ou Openssl, puis Network Manager va devoir appeler systemd-resolved ainsi que dhclient qui peuvent tout deux envoyer des requêtes au driver wlan en même temps que wpa_supplicant. Cela peut provoquer des problèmes de performances dont des baisses de débit, ou des déconnexions.

Et c’est justement pour éviter tous ces problèmes qu’iwd a été développé.

IWD

Iwd (iNet wireless daemon) est un wireless démon pour Linux écrit par Intel et qui vise à remplacer wpa_supplicant. L’objectif principal du projet est d’optimiser l’utilisation des ressources en ne dépendant d’aucune bibliothèque externe à part dbus et en utilisant au maximum les fonctionnalités fournies par le noyau Linux. Iwd mémorise les paramètres, ce sans l’aide de Network Manager ou équivalent. Un des buts est donc de centraliser au maximum tout ce dont le Wi-Fi a besoin au sein d’iwd.

Par exemple iwd supporte la configuration des interfaces Wi-Fi en standalone. Le but ici est de se passer de :

  • systemd-networkd/netplan/networking
  • dhclient
  • systemd-resolved/resolvconf

Un autre exemple est la configuration d’un réseau entreprise. Pour configurer un réseau entreprise sous Linux il faut que l’utilisateur fournisse un certificat CA, un certificat utilisateur ainsi que sa clé privée et son identifiant (dans le cas EAP-TLS) :

UI de l’applet de Network Manager pour la configuration d’un réseau entreprise

L’idée d’iwd est de tout centraliser au sein d’un seul et unique fichier qui pourrait être importé en un clic. L’utilisateur n’a donc plus qu’à demander à son administrateur réseau de lui envoyer le fichier qu’il peut alors importer simplement (un peu de la même manière qu’un fichier ovpn pour la configuration d’un client openvpn). Toute la configuration dont le certificat CA, le certificat utilisateur et sa clé privée est alors formatée dans un seul fichier au format standard.

Par ailleurs, la décision de ne pas se soucier de fonctionner sur des distributions non Linux a été prise. Dbus est utilisé pour la communication et les interfaces cryptographiques du noyau sont utilisées pour la fonctionnalité de chiffrement.

Compilation et utilisation

Compilation

Avant toute chose, pour que iwd puisse être exécuté sur votre système certaines options de compilation de votre noyau sont nécessaires :

CONFIG_CRYPTO_USER_API_HASH
CONFIG_KEY_DH_OPERATIONS
CONFIG_CRYPTO_ECB
CONFIG_CRYPTO_MD5
CONFIG_CRYPTO_CBC
CONFIG_CRYPTO_SHA256
CONFIG_CRYPTO_AES
CONFIG_CRYPTO_DES
CONFIG_CRYPTO_USER_API_SKCIPHER
CONFIG_CRYPTO_CMAC
CONFIG_CRYPTO_HMAC
CONFIG_CRYPTO_SHA512
CONFIG_CRYPTO_ARC4
CONFIG_CRYPTO_SHA1

De la même manière que wpa_supplicant, la compilation iwd génère plusieurs binaires : le démon iwd, le client iwctl et également iwmon, un utilitaire qui permet de lire des fichiers .pcap..

Vu que iwd est bien plus minimaliste que wpa_supplicant, il n’a pas à être configuré comme wpa_supplicant via un fichier .config. La plupart des options de compilation possibles concernent uniquement la compilation et l’installation d’outils tiers, de fichier de config de systemd ou l’utilisation de iwmon.

Note : Comme tout bon projet iwd utilise un générateur de Makefile, ici autotools. La compilation du projet est claire et sans bavure, la structure du projet est simple et une recette Yocto est déjà présente dans meta-openembedded. Néanmoins, il manque la ligne suivante dans le recette de Yocto Warrior (corrigé depuis sur Dunfell) :

RDEPENDS += "dbus"

Utilisation

La configuration d’iwd en standalone se veut bien plus simple que celle avec wpa_cli et c’est le cas. Iwctl ne présente qu’une dizaine de commandes (plus d’une centaine pour wpa_cli), la configuration et la selection d’un réseau est extrêmement simplifiée. Par exemple :

iwctl
>> station wlan0 connect Livebox-titi

Permet de se connecter à la Livebox-titi (il faut 4 commandes pour wpa_cli) et le psk est demandé de manière interactive.

La connexion est d’ailleurs très rapide et c’est dû notamment à l’optimisation du scan faite par iwd. En effet, avant une connexion, plutôt que de scanner tous les Access Points sur l’ensemble des bandes de fréquences Wi-Fi, iwd va uniquement scanner le channel utilisé lors de la précédente connexion.

On remarquera néanmoins que certaines options de connexion ne sont pas possibles via iwctl. Par exemple la connexion à une bande de fréquence particulière, la priorité d’un réseau, l’utilisation de PMF (Protection Management Frame) ou la configuration d’un réseau entreprise passent obligatoirement par des fichiers de configurations.

Un fichier de configuration global main.conf est présent dans /etc/iwd et les fichiers de configuration spécifiques aux réseaux sont présents dans /var/lib/iwd . Fort heureusement iwd met à disposition un grand nombre d’exemples de fichiers de configuration dans son répertoire de test iwd/autotests.

Une option très intéressante configurable depuis une fichier de configuration est le paramètre « EnableNetworkConfiguration »

cat /etc/iwd/main.conf
[General]
EnableNetworkConfiguration=true

Celle ci permet de remplacer systemd-networkd/networking/netplan en assignant les paramètres de l’interface directement dans le fichier de configuration de l’Access Point.

cat /var/lib/iwd/Livebox-titi.psk
[Security]
PreSharedKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Passphrase=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

[IPv4]
ip=192.168.2.42
netmask=255.255.255.0
gateway=192.168.2.1
broadcast=192.168.2.255
dns=192.168.2.1

Une fois systemd-networkd désactivé, ces paramètres vont être pris en compte lors de la connexion. Cela peut être extrêmement pratique dans le cadre d’un système embarqué qui ne possède que des interfaces wifi. Il sera alors possible de laisser iwd « se charger de tout ».

Limites

De ce que j’ai pu en tester iwd tient clairement ses promesses : la configuration est simplifiée, le logiciel est bien plus performant que wpa_supplicant. Néanmoins il est clair que le public visé par Intel est le desktop Linux. Même si il permet une solution ultra minimaliste, la dépendance obligatoire à dbus pourrait être un facteur limitant.

Un autre facteur limitant est le fait qu’il ne soit pas aussi facile d’écrire son propre client avec iwd. Même si c’est un cas assez spécifique, si vous travaillez sur une gateway par exemple et que vous devez développer votre propre version de NetworkManager il sera alors obligatoire de développer votre propre client wpa. Bien que cela soit possible en prenant l’exemple d’iwctl la documentation manque probablement de quelques exemples triviaux.

Conclusion

Encore utilisé de base sur la plupart des distributions, il est clair que wpa_supplicant va bientôt céder sa place à iwd après plus de 15 ans de bons et loyaux services. Plus performant et plus facilement configurable, iwd a pour vocation non seulement d’améliorer le Wi-Fi sous Linux mais également toute la stack réseau.

Même si la dépendance à dbus peut être un frein, un projet de iwd sans cette dépendance est en cours de développement : https://github.com/dylanaraps/eiwd . Iwd et iwmon sont parfaitement fonctionnels mais iwd ne peut pas être configuré via iwctl, la configuration passe alors obligatoirement par ses fichiers de configuration.