Gestion des périphériques USB sous [Free|Net|Open]BSD

Magazine
Marque
GNU/Linux Magazine
Numéro
268
Mois de parution
mars 2024
Spécialité(s)


Résumé

Lorsqu'on fait connaissance avec l'un des systèmes héritiers du BSD d'origine, à savoir FreeBSD, NetBSD ou encore OpenBSD, tout en ayant une certaine expérience de GNU/Linux, il est relativement facile de retrouver ses petits. Certes, depuis quelques années, le système d'init et la gestion des services de GNU/Linux se sont drastiquement écartés des principes propres à la philosophie Unix, jusqu'alors respectés par GNU/Linux. Dans l'ensemble, la transition est relativement aisée, mais il y a cependant un point sur lequel les différences sont telles que quelques explications s'avèrent nécessaires : la gestion des périphériques « hotplug » (USB) et de leurs permissions.


Body

GNU/Linux utilise depuis de nombreuses années un système appelé udev, permettant de réagir à des événements concernant les périphériques. C'est typiquement ce qu'on pourrait appeler un « gestionnaire de périphériques », même si cette terminologie n'est pas sans rappeler un système tristement populaire et propriétaire. udev a remplacé devfs il y a fort longtemps et est, depuis 2012, intégré et fortement lié à systemd (qu'est-ce qui ne l'est pas, actuellement ?). Son travail est simple puisqu'il s'agit, le plus souvent, de détecter l'apparition de périphériques et de déclencher des actions en conséquence, le tout en se basant sur un ensemble de règles généralement placées dans /etc/udev/rules.d. Là, on détermine le « quoi », le « qui », le « où » et, en fonction de ces informations, on ajuste les permissions, on crée des liens symboliques ou encore, on déclenche l'exécution de scripts (voire pire, merci, D-Bus).

Mais sous *BSD, point de systemd tentaculaire (Dieu merci) et donc, point de udev. Contrairement aux quelques distributions GNU/Linux exemptes de systemd (la documentation Gentoo détaille bien les implications [1]), il n'y a cependant ici aucune obligation de compatibilité. L'utilisateur devra s'adapter au système et non tenter de retrouver quelque chose qu'il connaît déjà, sinon à quoi bon passer de GNU/Linux à un autre système ? Et ces autres systèmes ont, sur ce point comme d'autres (l'init, les services, la configuration globale, la division entre le système et les applications tierces, etc.), une approche radicalement différente, plus orientée vers l'administration effective du système et une réelle implication de la personne en charge de sa configuration. En d'autres termes, un « BSD » repose sur la compétence, la créativité et l'envie (le besoin ?) de personnaliser le système pour le rendre plus efficace et maîtrisable.

En ce sens, FreeBSD, NetBSD et OpenBSD fournissent les bases, chacune différente mais similaire aux autres sur le principe, de quelque chose qui se rapproche d'udev, mais offre la possibilité (et implicitement l'obligation) d'adapter tout cela en fonction de ses préférences. Ma problématique première ici concerne l'accès à certains périphériques USB accessibles au travers d'outils reposant sur la LibUSB (lecteur NFC, devkit STM32, Raspberry Pi Pico via picotool, etc.), ainsi que les classiques convertisseurs USB/série permettant de dialoguer avec des systèmes embarqués et/ou des montages utilisant des microcontrôleurs. L'accès aux périphériques n'est pas un problème, mais leur identification et la gestion des permissions est un point à solutionner, car utiliser sudo ou doas systématiquement est non seulement rapidement pénible, mais également peu pertinent. J'écarterai ici, en revanche, les problématiques de montage automatique de systèmes de fichiers (sur clé USB), qui n'ont, à mon sens, que peu d'intérêt. Mais c'est une vision toute personnelle et vous adapterez ce qui suit selon vos préférences...

1. FreeBSD : devd

FreeBSD est sans doute le système le plus « avancé » à ce niveau. Je n'aime pas particulièrement ce terme, impliquant que les deux autres OS sont « en retard », mais nous avons ici que peu de choses à faire. FreeBSD utilise un daemon appelé devd, ainsi qu'un système de fichiers spécifique, devfs, donnant accès aux périphériques, classiquement via le point de montage /dev.

devd voit son comportement réglé par le fichier /etc/devd.conf, lui-même incluant les configurations présentes dans /etc/devd/*. Une rapide consultation des fichiers déjà en présence montre que devd est extrêmement générique et se chargera de réagir à tous types d'événements détectés et traités par le noyau. Le binaire en espace utilisateur utilise pour cela le périphérique devctl qui fournit des informations lorsqu'un nœud est ajouté ou supprimé dans l'arborescence de périphériques maintenue par le noyau.

Ici, nous nous intéressons en particulier aux périphériques USB et série, mais comme le montre le contenu de /etc/devd.conf, il est parfaitement possible de réagir, par exemple, au fait qu'un périphérique Ethernet voit sa liaison passer à « UP » et, de ce fait, lancer le client DHCP :

notify 0 {
   match "system"          "IFNET";
   match "type"            "LINK_UP";
   media-type              "ethernet";
   action "service dhclient quietstart $subsystem";
};

Si vous êtes coutumier d'udev, ceci devrait vous rappeler quelque chose. En effet, on retrouve ici un sous-système spécifique (IFNET), un type d'événement (LINK_UP), un type de média (ethernet) et une action à déclencher.

Un peu plus loin, on observe le même type de configuration pour, par exemple, changer le profil de gestion d'alimentation lorsque l'adaptateur secteur est connecté (sur un laptop, bien entendu) :

notify 10 {
   match "system"          "ACPI";
   match "subsystem"       "ACAD";
   action "service power_profile $notify";
};

La page de manuel de devd.conf décrit de façon exhaustive l'ensemble des informations utilisables, qu'il s'agisse des directives, des sous-systèmes, des variables ou encore du type d'événements sur lesquels réagir (en fonction du sous-système). Elle fournit également un certain nombre d'exemples qu'on retrouve dans le devd.conf installé par défaut.

Une fois tout ceci assimilé, il est relativement simple de composer ses propres configurations visant des périphériques précis, qu'on placera dans un fichier au nom explicite et avec un suffixe .conf dans /etc/devd. Pour les adaptateurs USB/série, nous nous attachons à la création des pseudo-fichiers /dev/ttyU* et changeons simplement les permissions de manière à permettre aux membres du groupe wheel de lire et d’écrire sur le périphérique :

# Serial ports
notify 100 {
        match "system"          "DEVFS";
        match "subsystem"       "CDEV";
        match "type"            "CREATE";
        match "cdev"            "ttyU[0-9]+";
        action "chmod 660 /dev/$cdev";
        action "logger change perm. for Serial on $cdev";
};

Nous observons devfs et réagissons sur la création d'un périphérique de caractères dont le nom débute par ttyU (notez que les expressions régulières utilisées sont implicitement entourées de ^$). En plus de changer les permissions, nous demandons l'ajout d'une entrée dans le log système (/var/log/messages), fort utile pour mettre au point les nouvelles configurations.

Le saviez-vous ?

Le nom du groupe wheel est un héritage du système d'exploitation TENEX pour PDP-10 à la fin des années 60 où un bit spécifique d'une configuration de compte utilisateur (le wheel bit) conférait des privilèges particuliers permettant de lancer certaines commandes auxquelles les utilisateurs standard n'avaient pas accès. « wheel » est une abréviation de « big wheel » désignant, en argot anglais, quelqu'un d'important. Ce qu'on pourrait traduire maladroitement en français par « une pointure » ou un « gros bonnet ». Le terme a survécu dans la culture Unix, puis BSD, lorsque les utilisateurs et développeurs provenant de TENEX ont migré en masse sous Unix dans les années 80.

Pour les périphériques USB génériques, ici deux lecteurs NFC et une Raspberry Pi Pico (en mode BOOT0), les choses se présentent plus ou moins de la même manière, si ce n'est que nous surveillons le sous-système « périphérique USB » et guettons une connexion (physique). Pour identifier les périphériques, nous nous basons naturellement sur les VendorID et ProductID :

# ASK LoGO NFC Reader
notify 100 {
        match "system" "USB";
        match "subsystem" "DEVICE";
        match "type" "ATTACH";
        match "vendor" "0x1fd3";
        match "product" "0x0608";
        action "chmod 660 /dev/$cdev";
        action "logger change perm. for NFC reader on $cdev";
};
 
# SCM Micro SCL3711-NFCRW
notify 100 {
        match "system" "USB";
        match "subsystem" "DEVICE";
        match "type" "ATTACH";
        match "vendor" "0x04e6";
        match "product" "0x5591";
        action "chmod 660 /dev/$cdev";
        action "logger change perm. for NFC reader on $cdev";
};
 
# Raspberry Pi Pico
notify 100 {
        match "system" "USB";
        match "subsystem" "DEVICE";
        match "type" "ATTACH";
        match "vendor" "0x2e8a";
        match "product" "0x0003";
        action "chmod 660 /dev/$cdev";
        action "logger change perm. for RPI Pico on $cdev";
};

Remarquez le notify en début de chaque bloc et dont nous n'avons pas encore parlé. Il s'agit d'une priorité d'une valeur arbitrairement choisie (0 étant la priorité plus basse) permettant de gérer une situation où deux configurations correspondraient à un même événement. Dans ce cas, seule la configuration ayant la priorité la plus élevée sera utilisée et non les autres. Ceci permet de mettre en place des configurations suffisamment génériques pour couvrir une vaste gamme de situations, tout en permettant de passer outre et d’appliquer une configuration spécifique dans les cas particuliers.

Une fois ces règles/configurations enregistrées et le service redémarré (avec sudo service devd restart), nous remarquons dans les journaux système que les $cdev pour deux lecteurs NFC sont ugenX.Y et donc que les entrées /dev/ correspondantes sont nommées identiquement. De façon similaire, une Raspberry Pi Pico connectée en mode BOOT0 (programmation) apparaît à la fois comme un périphérique de stockage USB (umass*) et comme un périphérique ugenX.Y. Ceci parce que ce périphérique USB propose deux interfaces différentes, c'est un périphérique composite.

Les entrées ugen correspondent à des périphériques USB n'ayant pas de pilotes spécifiques attachés par le noyau. On peut voir cela comme un pilote par défaut permettant à des outils en espace utilisateur de gérer directement le périphérique. C'est le cas par exemple de la quasi-totalité des lecteurs NFC qui sont alors pilotables avec la LibNFC, elle-même reposant sur la LibUSB. Dans le cas de la Pico, une des interfaces est directement prise en charge par le pilote umass et l'autre est vendor specific. Cette partie est alors utilisable avec un outil comme picotool, à condition que les permissions sur l'entrée /dev le permettent. Et c'est exactement l'objet de la règle devd que nous avons mise en place.

Notez que sous FreeBSD, les entrées /dev/ugen* sont en réalité des liens symboliques vers des pseudo-fichiers /dev/usb/*, et que c'est effectivement là que sont ajustées les permissions. Ceci deviendra important par la suite, car les deux autres systèmes ne fonctionnent pas exactement de la même manière sur ce plan.

2. NetBSD : devpubd

NetBSD utilise, lui aussi, un daemon permettant de déclencher des actions à l'apparition et à la disparition de périphériques, c'est devpubd. Le service doit être configuré pour démarrer en même temps que le système via une ligne dans /etc/rc.conf (devpubd=YES), ou être lancé manuellement avec sudo service devpubd onestart. Le principe de fonctionnement est identique à celui observé sous FreeBSD, mais sensiblement moins intégré. Lors d'un événement, le daemon lance par défaut le script /libexec/devpubd-run-hooks, qui lui-même exécute tous les scripts placés dans /libexec/devpubd-hooks, par ordre lexicographique (alphabétique, mais prenant aussi en compte les chiffres et caractères accentués) en leur passant un paramètre device-attach ou device-detach, ainsi que le nom du périphérique.

Deux scripts sont installés par défaut (NetBSD 9.3) :

  • 01-makedev permettant de créer des entrées dans /dev via MAKEDEV (NetBSD n'utilise pas un système de fichiers virtuel pour /dev/) ;
  • 02-wedgenames créant automatiquement des liens symboliques vers les zones d'un ou des disques (wedge) qui peuvent avoir été détectées automatiquement par le noyau (en fonction des partitions ou des disklabels) ou configurées manuellement avec dkctl. C'est un peu le /dev/disk/by-* qu'on retrouve sous GNU/Linux). Notez que les wedges eux-mêmes prennent la forme de /dev/dk*, et que ce script ne fait que leur donner des noms sous forme de liens symboliques dans /dev/wedges.

Pour ajuster nos permissions avec les deux types de périphériques qui nous intéressent, ceci est relativement simple puisqu'il nous suffit de créer deux nouveaux scripts dans /libexec/devpubd-hooks, le premier pour les ports série USB et le second pour les périphériques génériques (ugen comme sous FreeBSD). Le premier est relativement simple (03-adjustTTYU) :

#!/bin/sh
 
event="$1"
shift
devices=$@
 
for device in $devices; do
  case $device in
  ucom[0-9]*)
    unit=${device#ucom}
    case $event in
    device-attach)
      logger -s "devpubd: /dev/ttyU$unit attached"
      chmod g+rw /dev/ttyU$unit
      ;;
    device-detach)
      logger -s "devpubd: /dev/ttyU$unit detached !"
      chmod 600 /dev/ttyU$unit
      ;;
    esac
    ;;
  esac
done

Vous remarquerez que le nombre de paramètres est drastiquement inférieur à ce qui existe pour FreeBSD. Ici, nous n'avons que l'événement (un sur les deux possibles) et la désignation du périphérique. Mais comme il s'agit de shell, nous pouvons toujours faire plus ou moins ce qui nous chante. Nous déduisons donc le nom de l'entrée /dev en fonction du numéro de périphérique ucom (USB tty)) qui vient d'être détecté, et nous utilisons chmod pour attribuer les permissions en lecture/écriture pour le groupe (wheel).

Pour les périphériques ugen qui fonctionnent sur le même principe que ceux de FreeBSD (un pilote USB par défaut si aucun autre ne s'applique), les choses sont un peu plus délicates.

En effet, nous pouvons parfaitement procéder de même, mais nous n'aurons aucune distinction entre les périphériques, tout ce qui est connecté en USB et non pris en charge par un autre pilote sera donc impacté et traité, indifféremment. Ce n'est pas vraiment ce que nous voulons, même si cela ferait parfaitement le travail. C'est un peu trop brutal (même pour moi). devpubd ne remonte pas les informations pouvant nous être utiles, comme les VID:PID du périphérique. Pour cela, nous avons deux options : modifier /usr/src/sbin/devpubd/devpubd.c qui est relativement simple et utilise /dev/drvctl pour être informé des événements noyau, ou développer un outil qui va accéder au pseudo-fichier /dev/ugen* pour obtenir les informations. C'est cette seconde approche que j'ai choisie (mais maintenant que j'y pense, la première me tente bien aussi).

2.1 chkugenids à la rescousse !

ugen est bien plus qu'un pilote de remplacement pour les périphériques USB n'ayant pas de support dans le noyau. Un petit coup d’œil à la page de manuel et on se rend compte que c'est véritablement un point d'accès permettant à l'espace utilisateur de dialoguer et de commander le périphérique, et ce, directement. La classique LibUSB n'est là que pour simplifier, et unifier, les opérations via une couche d'abstraction, mais dans l'absolu, vous pourriez attaquer le pseudo-fichier sans libs. Un ensemble d'ioctl sont directement décrits dans la manpage, permettant de dialoguer avec le matériel (USB_DO_REQUEST), d'explorer les « descriptions » (périphérique, configurations, interfaces, endpoints) ou, tout simplement, d'obtenir des informations (USB_GET_DEVICEINFO).

Et c'est précisément là qu'il est possible d'agir. En effet, comme devpubd ne nous remonte pas les informations utiles, si ce n'est le nom du périphérique (et donc l'entrée /dev/ correspondante), nous pouvons tout simplement y accéder pour collecter des informations. L'ioctl nous remplira une structure usb_device_info, ressemblant à ceci :

struct usb_device_info {                                      
    uint8_t     udi_bus;
    uint8_t     udi_addr;
    usb_event_cookie_t udi_cookie;
    char        udi_product[USB_MAX_ENCODED_STRING_LEN];
    char        udi_vendor[USB_MAX_ENCODED_STRING_LEN];
    char        udi_release[8];
    char        udi_serial[USB_MAX_ENCODED_STRING_LEN];
    uint16_t    udi_productNo;
    uint16_t    udi_vendorNo;
    uint16_t    udi_releaseNo;
    uint8_t     udi_class;
    uint8_t     udi_subclass;
    uint8_t     udi_protocol;
    uint8_t     udi_config;
    uint8_t     udi_speed;
    int         udi_power;
    int         udi_nports;
    char        udi_devnames[USB_MAX_DEVNAMES][USB_MAX_DEVNAMELEN];
    uint8_t     udi_ports[16];
};

Plus, une tripotée de macros :

#define USB_MAX_DEVNAMES 4
#define USB_MAX_DEVNAMELEN 16
#define USB_SPEED_LOW 1
#define USB_SPEED_FULL 2
#define USB_SPEED_HIGH 3
#define USB_SPEED_SUPER 4
#define USB_SPEED_SUPER_PLUS 5
#define USB_IS_SS(X) ((X) == USB_SPEED_SUPER || (X) == USB_SPEED_SUPER_PLUS)
#define USB_PORT_ENABLED 0xff
#define USB_PORT_SUSPENDED 0xfe
#define USB_PORT_POWERED 0xfd
#define USB_PORT_DISABLED 0xfc

Créer un outil prenant en argument quelques options et un chemin vers une entrée /dev/ugenN.EE, où X est un numéro de périphérique et EE celui d'un endpoint, et nous retournant une information bien précise, comme un vendorID et/ou un deviceID USB, n'est pas bien difficile. Toutes les informations utiles se trouvent dans la struct usb_device_info. Il nous suffit donc d'ouvrir le pseudo-fichier :

    if ((fd=open(filepath, O_RDONLY)) == -1) {
        if(!optquiet)
            err(EXIT_FAILURE, "Open Error");
        exit(EXIT_FAILURE);
    }

Utiliser l'ioctl :

    if (ioctl(fd, USB_GET_DEVICEINFO, &info) == -1) {
        close(fd);
        if(!optquiet)
            err(EXIT_FAILURE, "USB_GET_DEVICEINFO Error");
        exit(EXIT_FAILURE);
    }

Et explorer le contenu de la structure pour retourner à l'utilisateur la donnée demandée. On arrive ainsi à obtenir différentes sorties :

$ sudo chkugenids -f /dev/ugen1.00 -l
04e6:5591:SCM Micro:SCL3711-NFC&RW::3:2:0:0:100:2
 
$ sudo chkugenids -f /dev/ugen1.00 -a
IDs     : 04e6:5591
Vendor  : SCM Micro
Product : SCL3711-NFC&RW
Serial  :
Bus     : 3
Address : 2
Class   : 0
Sudclass: 0
Power   : 100 mA
Speed   : 2 (Full Speed - 12 Mbps)
 
$ sudo chkugenids -f /dev/ugen1.00 -p
SCL3711-NFC&RW
 
$ sudo chkugenids -f /dev/ugen1.00 -i
04e6:5591

Partant de là, et après avoir intégré le binaire correctement dans le système (avec pkgsrc-wip, par exemple), nous pouvons produire un petit script :

#!/bin/sh
#
# Change permissions on /dev/ugenN.EE
#
 
event="$1"
shift
devices=$@
 
# list of VIP:PID for NFC readers
IDS="
04cc:0531
054c:0193
04cc:2533
072f:2200
1fd3:0608
04e6:5591
2e8a:0003
"
 
CHKUGENIDS=/usr/pkg/sbin/chkugenids
 
for device in $devices; do
  case $device in
  ugen*)
    case $event in
    device-attach)
      logger -s "ugensetperm: $device attached"
      DEVID=`$CHKUGENIDS -f /dev/$device.00 -i -q`
      for i in $IDS
      do
        if [ "$DEVID" = "$i" ]
        then
          logger -s "ugensetperm: match, adj. perm. on /dev/$device.*"
          chmod g+rw /dev/$device.*
        fi
      done
      ;;
    device-detach)
      logger -s "ugensetperm: $device detached. Setting back perm."
      chmod 600 /dev/$device.*
      ;;
    esac
    ;;
  esac
done

Le script est relativement simple puisqu'à l'arrivée d'un nouveau périphérique ugen, l'entrée /dev/ correspondante est déduite puis utilisée avec chkugenids pour obtenir les informations détaillées. Celles-ci sont ensuite comparées, dans une boucle for, avec une liste d'ID et, si une correspondance est validée, les permissions sont modifiées. Ainsi, si un périphérique ugen n'est pas dans la liste, il est simplement ignoré. Nous avons maintenant le niveau de granularité nécessaire, avec un /dev statique, mais presque équivalent à ce qu'on trouve dans FreeBSD (au niveau de ugen uniquement).

Les sources de chkugenids sont disponibles, sous licence BSD 2-Clause, dans mon GitLab [2] et un port pkgsrc est présent dans pkgsrc-wip [3].

3. OpenBSD : hotplugd

La solution d'OpenBSD est assez similaire à celle de NetBSD, ce qui semble normal étant donné l'origine du système, mais un certain nombre de différences sont présentes et importantes. Ici, il ne s'agit pas de devpubd mais de hotplugd, qui sera activé d'un simple rcctl enable hotplugd (en root, bien entendu). Il s'agit, là aussi, d'un daemon qui va se charger de scruter l'activité du noyau, mais ici via le pseudo-fichier /dev/hotplug, puis utiliser les événements détectés pour appeler l'un ou l'autre des scripts, /etc/hotplug/attach ou /etc/hotplug/detach. Comme vous pouvez le constater, ici la division entre un type d'événement ou un autre est déjà faite, mais deux arguments sont cependant passés à l'un ou l'autre script :

  • une classe de périphérique, correspondant à ce qui est décrit dans sys/device.h ;
  • et un nom de périphérique tel qu'utilisé, par exemple, par MAKEDEV, complété d'un numéro.

On retrouve également ugen, représentant les périphériques USB n'ayant de pilotes dédiés et c'est bien ainsi qu'on retrouve, par exemple, un lecteur NFC USB. La page de manuel de hotplugd donne un exemple de script à partir duquel on pourra rapidement construire un squelette pouvant servir de base de travail :

#!/bin/sh
 
DEVCLASS=$1
DEVNAME=$2
 
# The device class (sys/device.h):
# 0 generic, no special info
# 1 CPU (carries resource utilization)
# 2 disk drive
# 3 network interface
# 4 tape device
# 5 serial line interface
 
case $DEVCLASS in
  0)
    # generic, no special info
    ;;
  1)
    # CPU (carries resource utilization)
    ;;
  2)
    # disk drive
    ;;
  3)
    # network interface
    ;;
  4)
    # tape device
    ;;
  5)
    # serial line interface
    DEVNUM=$(echo ${DEVNAME} | sed 's/[a-z]//g')
    logger -s "HotPlug script: /dev/ttyU${DEVNUM} ($DEVNAME) attached"
    ;;
esac

Nous avons déjà ajouté le nécessaire pour réagir à l'apparition d'un nouveau convertisseur USB/série, mais étant donné que les /dev/ttyU* ont déjà pour groupe dialer avec des permissions 660, nous n'avons rien à faire, si ce n'est de mettre le bon utilisateur (moi) dans ce groupe. Le présent script se contentera donc d'indiquer l'événement dans /var/log/messages :

posson /bsd: uftdi0 at uhub1 port 1 configuration 1
  interface 0 "FTDI FT232R USB UART" rev 2.00/6.00 addr 2
posson /bsd: ucom0 at uftdi0 portno 1
posson root: HotPlug script: uftdi0 attached
posson root: HotPlug script: /dev/ttyU0 (ucom0) attached

Oui, ma machine OpenBSD s'appelle « Posson », parce que c'est amusant (si, si, je vous assure, ça me fait sourire à chaque login). Ce qui l'est moins en revanche, c'est ce qui se passe lorsqu'on tente d'appliquer la même stratégie que celle utilisée avec NetBSD, où chkugenids entre en jeu. Bien sûr, le code fonctionnera de la même manière et nous permettra d'identifier le périphérique via ses ID. Les permissions sont alors bien changées sur les /dev/ugenN.* correspondants, mais :

$ ls -l /dev/ugen1.*
6584507 crw-rw---- 1 root wheel   63, 16 Mar 20 14:01 /dev/ugen1.00
6584508 crw-rw---- 1 root wheel   63, 17 Mar 2 19:03 /dev/ugen1.01
6584509 crw-rw---- 1 root wheel   63, 18 Mar 2 19:03 /dev/ugen1.02
[...]
6584520 crw-rw---- 1 root wheel   63, 29 Mar 2 19:03 /dev/ugen1.13
6584521 crw-rw---- 1 root wheel   63, 30 Mar 2 19:03 /dev/ugen1.14
6584522 crw-rw---- 1 root wheel   63, 31 Mar 2 19:03 /dev/ugen1.15
 
$ nfc-list
nfc-list uses libnfc 1.8.0
No NFC device found.
 
$ sudo nfc-list
nfc-list uses libnfc 1.8.0
NFC device: SCM Micro / SCL3711-NFC&RW opened

En fouillant un peu, on se rend alors compte que donner des permissions en écriture sur /dev/ugenN.* n'est pas suffisant. Nous avons aussi /dev/usb* qui entre en jeu et la commande usbdevs nous apporte des compléments d'information intéressants :

$ usbdevs
Controller /dev/usb0:
addr 01: 8086:0000 Intel, EHCI root hub
addr 02: 0ac8:c33f Namuga., WebCam SCB-0340N
Controller /dev/usb1:
addr 01: 8086:0000 Intel, UHCI root hub
addr 02: 0eef:480e eGalax Inc., USB TouchController
Controller /dev/usb2:
addr 01: 8086:0000 Intel, UHCI root hub
addr 02: 0403:6001 FTDI, FT232R USB UART
Controller /dev/usb3:
addr 01: 8086:0000 Intel, UHCI root hub
addr 02: 0a5c:219b Broadcom Corp, Broadcom Bluetooth 2.1 Device
Controller /dev/usb4:
addr 01: 8086:0000 Intel, UHCI root hub
addr 02: 04e6:5591 SCM Micro, SCL3711-NFC&RW

Notre périphérique est bien là, à l'adresse 2 sur le cinquième contrôleur USB de la machine (/dev/usb4). Changer les permissions sur ce fichier, en autorisant les accès lecture/écriture au groupe wheel, dont je fais partie, règle parfaitement le problème et permet immédiatement d'utiliser nfc-list. En tout état de cause, il semblerait donc que notre script hotplugd doive également changer les permissions sur l'entrée correspondante (le problème semble venir en réalité de la LibUSB, sous-jacente à la LibNFC, qui génère des I/O nécessitant ces permissions. Ceci demandera davantage d'investigations).

La question est donc alors : comment trouver quelle entrée dans /dev correspond effectivement à notre périphérique étant apparu sous un nom ugenN.EE ? Et la réponse est un nouvel outil.

3.1 findugendev, le copain de chkugenids

En dehors du fait que je ne suis vraisemblablement pas doué pour trouver des noms à mes outils, nous n'avons ici rien de bien exceptionnel par rapport à chkugenids. En réalité, le fonctionnement est quasi identique, si ce n'est que ce n'est pas un seul fichier /dev qui est ouvert, mais tous ceux susceptibles de correspondre à notre recherche, dans une simple boucle while sur ce que retourne opendir/readdir. Là, nous filtrons sur les noms « ugen » et « usb » pour utiliser l'ioctl adéquate.

Et ici, nous avons à la fois une subtilité et une chance, car nous avons USB_GET_DEVICEINFO pour /dev/ugen* et USB_DEVICEINFO pour /dev/usb*, mais les deux nous permettent d'obtenir la même struct usb_device_info. Attention cependant, les deux ioctl ne s'utilisent pas de la même manière. Dans le cas de USB_GET_DEVICEINFO, celle-ci est remplie automatiquement, mais comme un /dev/usb* est un point d'accès vers le contrôleur, et non un périphérique, nous devons, avec USB_DEVICEINFO, spécifier à quel périphérique nous nous adressons. Ceci se fait en renseignant le membre usb_device_info.udi_addr avant l'appel à l'ioctl, ce que nous pouvons faire sous la forme d'une boucle :

for (uint8_t addr = 1; addr < USB_MAX_DEVICES; addr++) {
    devinfo.udi_addr = addr;
    if (ioctl(fd, USB_DEVICEINFO, &devinfo) == -1) {
        if (optverb)
            warn("%s: ioctl failed.", filename);
        continue;
    }
[...]

Nous essayons toutes les adresses possibles (jusqu'à USB_MAX_DEVICES) et si l'ioctl fonctionne, alors nous pouvons inspecter le contenu de la struct devinfo qui aura été complétée. En dehors de ce point, le processus est donc assez simple et très similaire à chkugenids. De plus, findugendev fonctionnera également sous NetBSD (et même FreeBSD, tout comme chkugenids).

Nous pouvons alors combiner les deux outils pour composer notre script attach comme suit :

#!/bin/sh
 
DEVCLASS=$1
DEVNAME=$2
 
# list of VIP:PID for NFC readers
IDS="
04cc:0531
054c:0193
04cc:2533
072f:2200
1fd3:0608
04e6:5591
"
 
case $DEVCLASS in
  0)
    # generic, no special info
    logger -s "HotPlug script: $DEVNAME attached"
    DEVID=`chkugenids -f /dev/$DEVNAME.00 -i -q`
    for i in $IDS
    do
      if [ "$DEVID" = "$i" ]
      then
        chmod g+rw /dev/$DEVNAME.*
        logger -s "HotPlug script: group perm. changed on $DEVNAME.*"
        for j in `findugendev -U -v ${DEVID%:*} -p ${DEVID#*:}`
        do
          chmod 660 $j
          logger -s "HotPlug script: group perm. changed on $j"
        done                    
      fi                
    done        
    ;;          
  1)    
    # CPU (carries resource utilization)
    ;;          
  2)    
    # disk drive
    ;;          
  3)    
    # network interface
    ;;          
  4)    
    # tape device
    ;;          
  5)
    # serial line interface
    DEVNUM=$(echo ${DEVNAME} | sed 's/[a-z]//g')
    logger -s "HotPlug script: /dev/ttyU${DEVNUM} ($DEVNAME) attached"
    ;;
esac

Nous n'avons même pas besoin de redémarrer hotplugd et le script sera directement utilisé. Et effectivement, la connexion d'un des périphériques listés dans le tableau permet d'appliquer les ajustements et donc de donner directement accès au matériel avec les outils complémentaires de la LibNFC :

denis@posson:~$ id
uid=1000(denis) gid=1000(denis) groups=1000(denis),
  0(wheel), 9(wsrc), 21(wobj), 117(dialer)
 
denis@posson:~$ nfc-list
nfc-list uses libnfc 1.8.0
NFC device: SCM Micro / SCL3711-NFC&RW opened
1 ISO14443A passive target(s) found:
ISO/IEC 14443A (106 kbps) target:
    ATQA (SENS_RES): 00 04
       UID (NFCID1): 01 3c 2e 26
      SAK (SEL_RES): 08

4. Limitations, if defined, et la vraie unique solution, ou pas

Vous me demanderez sans doute « Quid des déconnexions ? », en particulier dans ce dernier cas de figure, avec OpenBSD. Et vous aurez raison, puisque ceci n'est absolument pas traité et, de ce fait, conduira forcément, tôt ou tard, à avoir des permissions en lecture/écriture pour les membres du groupe wheel sur n'importe quel bus USB où un lecteur aura été connecté par le passé. C'est un problème non négligeable et difficilement corrigible avec OpenBSD. En effet, FreeBSD et NetBSD ont, respectivement, soit une gestion plus complète, soit aucun problème d'accès à /dev/usb*. Ils peuvent, de ce fait, parfaitement rétablir les permissions lors de la déconnexion, puisque l'entrée /dev/ugen* est également précisée.

Dans le cas d'OpenBSD, le périphérique n'est plus là et nous n'aurons alors aucun moyen de savoir où il était branché. Ainsi, à moins de conserver une trace dans un fichier temporaire, ce qui pose d'autres problèmes en termes de synchronisation (je ne parle même pas de plusieurs occurrences des mêmes VIP:PID), je ne vois pas vraiment comment aborder la problématique.

Les deux outils développés, que sont chkugenids [2] et findugendev [4], sont davantage de petits hacks rapides qu'une réelle solution. Il est intéressant, cependant, de constater que la proximité technique entre les trois OS permet de n'avoir qu'un seul fichier source et de gérer les headers et les cas particuliers au besoin. Exemple :

#include <sys/ioctl.h>
#if defined(__NetBSD__) || defined(__OpenBSD__)
#include <dev/usb/usb.h>
#else
#include <dev/usb/usb_ioctl.h>
#endif

usb_ioctl.h est le header de FreeBSD, car oui, les outils fonctionnent également avec ce système, même si usb_device_info est légèrement différent :

void printinfo(struct usb_device_info *devinfo)
{
 
    printf("IDs      : %04x:%04x\n",
       devinfo->udi_vendorNo, devinfo->udi_productNo);
    printf("Vendor   : %s\n", devinfo->udi_vendor);
    printf("Product  : %s\n", devinfo->udi_product);
    printf("Serial   : %s\n", devinfo->udi_serial);
    printf("Bus      : %u\n", devinfo->udi_bus);
    printf("Address  : %u\n", devinfo->udi_addr);
    printf("Class    : %u\n", devinfo->udi_class);
    printf("Sudclass : %u\n", devinfo->udi_subclass);
    printf("Power    : %d mA\n", devinfo->udi_power);
    printf("Speed    : %u ", devinfo->udi_speed);
    switch(devinfo->udi_speed) {
    case USB_SPEED_LOW:
        printf("(Low Speed - 1.5 Mbps)"); break;
    case USB_SPEED_FULL:
        printf("(Full Speed - 12 Mbps)"); break;
    case USB_SPEED_HIGH:
        printf("(High Speed - 480 Mbps)"); break;
    case USB_SPEED_SUPER:
       printf("(Super Speed - 5 Gbps)"); break;
#if defined(__NetBSD__)
    case USB_SPEED_SUPER_PLUS:
        printf("(Super Speed+ - 10 Gbps)"); break;
#endif
    default: printf("(unknown speed)");
    }
#if defined(__NetBSD__) || defined(__OpenBSD__)
    printf("\nDriver(s): ");
    for (int i = 0; i < USB_MAX_DEVNAMES; i++)
        if (devinfo->udi_devnames[i][0] != '\0')
            printf("%s ", devinfo->udi_devnames[i]);
#endif
    printf("\n\n");
}

En gérant judicieusement les tests, nous arrivons ainsi, en prime, à obtenir une alternative à lsusb (GNU/Linux), usbdevs (OpenBSD et NetBSD) ou usbconfig (FreeBSD), pour lister les périphériques. Ceci peut éventuellement être intéressant, même sans implication des daemons. Exemple :

$ sudo findugendev -ul
[...]
/dev/ugen1.00:04e6:5591:SCM Micro:SCL3711-NFC&RW::4:2:0:0:100:2
/dev/ugen2.00:2e8a:0003:Raspberry Pi:RP2 Boot:E0C912952D54:1:2:0:0:500:2
/dev/usb0:8086:0000:Intel:EHCI root hub::0:1:9:0:0:3
/dev/usb0:0ac8:c33f:Namuga.:WebCam SCB-0340N::0:2:239:2:128:3
/dev/usb1:8086:0000:Intel:UHCI root hub::1:1:9:0:0:2
/dev/usb1:2e8a:0003:Raspberry Pi:RP2 Boot:E0C912952D54:1:2:0:0:500:2
/dev/usb2:8086:0000:Intel:UHCI root hub::2:1:9:0:0:2
/dev/usb3:8086:0000:Intel:UHCI root hub::3:1:9:0:0:2
/dev/usb4:8086:0000:Intel:UHCI root hub::4:1:9:0:0:2
/dev/usb4:04e6:5591:SCM Micro:SCL3711-NFC&RW::4:2:0:0:100:2
 
$ sudo findugendev -ua
/dev/ugen0.00:
IDs      : 0a5c:219b
Vendor   : Broadcom Corp
Product  : Broadcom Bluetooth 2.1 Device
Serial   : 506313AE7C7E
Bus      : 3
Address  : 2
Class    : 224
Sudclass : 1
Power    : 0 mA
Speed    : 2 (Full Speed - 12 Mbps)
Driver(s): ugen0
 
[...]
 
/dev/usb1:
IDs      : 2e8a:0003
Vendor   : Raspberry Pi
Product  : RP2 Boot
Serial   : E0C912952D54
Bus      : 1
Address  : 2
Class    : 0
Sudclass : 0
Power    : 500 mA
Speed    : 2 (Full Speed - 12 Mbps)
Driver(s): umass0 ugen2
[...]

C'est un bonus sympathique (presque autant qu'inutile), mais en ce qui concerne NetBSD et OpenBSD, la réalité est qu'il faudrait soit porter le devd de FreeBSD (en prenant en compte le fait que /dev soit statique), soit faire évoluer devpubd et/ou hotplugd pour leur faire remonter davantage d'informations aux scripts, peut-être sous la forme d'une série clé/valeur sérialisée, changeant en fonction du type de périphérique concerné. Mais on parle ici de quelque chose de pleinement intégré au système, et qui nécessite donc une implication personnelle qui va au-delà du petit hack rapide...

5. Pour finir, est-ce bien nécessaire ?

Comprenez bien que cette approche, en particulier avec NetBSD et OpenBSD, est loin d'être raisonnablement nécessaire, si on y réfléchit un peu. En effet, ces deux systèmes utilisent un /dev entièrement statique et une politique de permissions réglera parfaitement le problème d'une façon plus simple. Il suffit de mettre les utilisateurs « méritants » dans le bon groupe et de changer les permissions de façon définitive, que ce soit sur /dev/ugen* ou /dev/usb* (dans le cas d'OpenBSD). Et rien ne vous empêche de créer un groupe usb ou usbdev, tout comme il existe déjà un groupe dialer (OpenBSD également).

Ce qui soulève alors une question d'ordre philosophique, voire psychologique : tenter d'améliorer cela en automatisant le changement de permissions (ou toutes autres manipulations qu'on peut imaginer sur la même base) n'est-elle pas simplement la manifestation d'un désir malsain, consistant à vouloir inconsciemment reproduire ce qu'on connaît déjà d'un autre système ? Dénaturant alors la réelle philosophie de l'OS, sous prétexte de vouloir corriger un problème qui n'en est peut-être pas un.

Ceci vous rappelle-t-il quelque chose ? Des histoires de systèmes d'init, peut-être ? Mais non, c'est bien plus vaste que cela. En rédigeant dernièrement un autre article, traitant de la manière d'installer ces trois OS sur une unique carte MMC à destination d'une Raspberry Pi 4, je me suis frotté aux « installeurs » de chacun d'eux. En intégrant mentalement également ceux que je connais déjà du monde GNU/Linux (Debian, Ubuntu, Mint, etc.), je pense qu'on peut réellement voir apparaître un motif assez intéressant et surtout une grande diversité dans la manière de traiter la question.

De l'installeur graphique lancé depuis une distribution live Mint 21.1 aux quelques questions très spartiatement posées par l'installeur OpenBSD, nous avons le choix. Mais l'objectif est rempli parfaitement dans tous les cas. Mieux encore, l'installeur ressemble au système et à ce sur quoi il met l'accent et à quoi il attache de l'importance.

Qu'il s'agisse de gestion de périphériques « à la udev » ou de l'installation, ceci n'est rien d'autre que l'expression de la personnalité du système. Et vouloir à tout prix la changer n'est peut-être pas la meilleure chose à faire, parce que le risque de « devenir envahissant » ou de tout changer est considérable. C'est une pente glissante que d'autres ont déjà empruntée, et on connaît le résultat (oui, là je pense à regedit^Wsystemd).

La conclusion de cette réflexion est évidente, à mon sens. Je me satisferai finalement de ce que proposent ces systèmes, sans chercher à changer (ou réimplémenter) devpubd et/ou hotplugd pour en faire des devd, ou pire, des udev. L'exploration de la mécanique de gestion, des sources existantes, des ioctl, et le plaisir d'implémenter deux utilitaires très modestes (à l'avenir incertain), est bien suffisant et permet déjà de toucher du doigt le réel but de l'opération : faire plus ample connaissance avec ces OS et en absorber la philosophie.

Références

[1] https://wiki.gentoo.org/wiki/Gentoo_Without_systemd#The_udev_situation

[2] https://gitlab.com/0xDRRB/chkugenids

[3] https://www.pkgsrc.org/wip/

[4] https://gitlab.com/0xDRRB/findugendev



Article rédigé par

Par le(s) même(s) auteur(s)

Effort maximum : OpenBSD sur une carte RISC-V 1 GHz/1 Gio à 30 €

Magazine
Marque
Hackable
Numéro
57
Mois de parution
novembre 2024
Spécialité(s)
Résumé

Dans un précédent édito, je râlais (comment ça, « comme toujours » ?) à propos de l'absence de « leader » dans le monde des SBC RISC-V, qui permettrait, à l'instar de Raspberry Pi, d'avoir au moins une plateforme de référence pour l'ensemble des projets open source. Pour asseoir ma complainte et démontrer cette carence, j'ai décidé d'opter pour une petite séance de masochisme en mode « poisson globe roulé sous les aisselles » en me penchant sur le SBC MangoPi MQ-Pro D1 pour y installer un OS qu'on trouve rarement dans l'embarqué...

Utiliser Mutt avec OAuth 2.0

Magazine
Marque
GNU/Linux Magazine
Numéro
272
Mois de parution
novembre 2024
Spécialité(s)
Résumé

Contribuer à des projets open source signifie implicitement rejoindre une communauté de développeurs et communiquer de façon efficace via l'infrastructure prédatant votre subite envie d'apporter votre pierre à l'édifice. Certains projets optent ou migrent vers des choses comme GitHub, Slack, Discord, et d'autres préfèrent raisonnablement rester sur Git, CVS, phpBB, Trac et surtout des listes de diffusion. Et qui dit « listes de diffusion » dit forcément « utiliser un MUA » (Mail User Agent) digne de ce nom, comme Mutt (ou NeoMutt) ». Seulement voilà, quand votre fournisseur de services de messagerie électronique impose des restrictions, c'est le drame !

FPGA facile : petite présentation et prise en main de LiteX

Magazine
Marque
Hackable
Numéro
57
Mois de parution
novembre 2024
Spécialité(s)
Résumé

LiteX est un projet qui aurait dû faire l'objet d'un article depuis bien longtemps, puisqu'il s'agit sans doute de la façon la plus simple d'aborder le monde des circuits logiques programmables (PLD), et des FPGA en particulier. Plus exactement, ce framework permet de composer son matériel avec des briques élémentaires, sans utiliser de Verilog ou de VHDL et d'obtenir, au final, un système embarqué ou un périphérique sur mesure qu'on peut ensuite programmer. Découvrons cela...

Les derniers articles Premiums

Les derniers articles Premium

La place de l’Intelligence Artificielle dans les entreprises

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

L’intelligence artificielle est en train de redéfinir le paysage professionnel. De l’automatisation des tâches répétitives à la cybersécurité, en passant par l’analyse des données, l’IA s’immisce dans tous les aspects de l’entreprise moderne. Toutefois, cette révolution technologique soulève des questions éthiques et sociétales, notamment sur l’avenir des emplois. Cet article se penche sur l’évolution de l’IA, ses applications variées, et les enjeux qu’elle engendre dans le monde du travail.

Petit guide d’outils open source pour le télétravail

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Ah le Covid ! Si en cette période de nombreux cas resurgissent, ce n’est rien comparé aux vagues que nous avons connues en 2020 et 2021. Ce fléau a contraint une large partie de la population à faire ce que tout le monde connaît sous le nom de télétravail. Nous avons dû changer nos habitudes et avons dû apprendre à utiliser de nombreux outils collaboratifs, de visioconférence, etc., dont tout le monde n’était pas habitué. Dans cet article, nous passons en revue quelques outils open source utiles pour le travail à la maison. En effet, pour les adeptes du costume en haut et du pyjama en bas, la communauté open source s’est démenée pour proposer des alternatives aux outils propriétaires et payants.

Sécurisez vos applications web : comment Symfony vous protège des menaces courantes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les frameworks tels que Symfony ont bouleversé le développement web en apportant une structure solide et des outils performants. Malgré ces qualités, nous pouvons découvrir d’innombrables vulnérabilités. Cet article met le doigt sur les failles de sécurité les plus fréquentes qui affectent même les environnements les plus robustes. De l’injection de requêtes à distance à l’exécution de scripts malveillants, découvrez comment ces failles peuvent mettre en péril vos applications et, surtout, comment vous en prémunir.

Bash des temps modernes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les scripts Shell, et Bash spécifiquement, demeurent un standard, de facto, de notre industrie. Ils forment un composant primordial de toute distribution Linux, mais c’est aussi un outil de prédilection pour implémenter de nombreuses tâches d’automatisation, en particulier dans le « Cloud », par eux-mêmes ou conjointement à des solutions telles que Ansible. Pour toutes ces raisons et bien d’autres encore, savoir les concevoir de manière robuste et idempotente est crucial.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 125 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous