All links of one day
in a single page.
<Previous day - Next day>

rss_feedDaily RSS Feed
floral_left The Daily Shaarli floral_right
——————————— Sunday 18, April 2021 ———————————

Comment fonctionne une capture réseau dans le noyau Linux ? À quel endroit du noyau le trafic est-il capturé par la libpcap ?

Je ne me souvenais plus : comment fonctionne une capture réseau avec la libpcap (tcpdump, wireshark, etc.) dans le noyau Linux ? À quel endroit du noyau le trafic est-il capturé par la libpcap ?

Explication concise dans les pages 10-12 de ce document que l'on peut recouper avec le code Linux de la libpcap.

En gros :

  • Création d'une socket de type RAW (entête Ethernet conservé), famille d'adresses AF/PF_PACKET (contournement de la partie TCP/IP de la pile réseau), protocole ETH_P_ALL (tous les protocoles). Dans le noyau (Linux), le protocole PF_PACKET, en tant que protocole générique, est traité très tôt par la pile réseau, après que le pilote de la carte réseau ait copié le paquet en RAM, et bien avant la couche IP (schéma) et Netfilter (le pare-feu de Linux). On voit la socket dans /proc/<PID_tcpdump>/fd ;

    • Détails : la carte réseau émet une IRQ matérielle. Son pilote la traite en créant un espace mémoire de type sk_buff (réprésentation noyau d'un paquet réseau), en copiant le paquet depuis le tampon de la carte réseau vers l'espace mémoire, et en appelant netif_rx(), la fonction noyau générique de réception d'un paquet réseau. Cette dernière gère la congestion (celle liée aux IRQ, pas la congestion TCP), met le traitement du paquet dans la file d'attente d'un CPU, et génère une IRQ logicielle (soft IRQ). La fonction net_rx_action() traite les IRQ logicielles liées au réseau. Entre autres, elle appelle la fonction qui permet de traiter le protocole niveau 3 (IP, IPX, etc.). ("Récemment", c'est aussi elle qui peut se retrouver à déclencher la fonction poll() de récupération d'un paquet dans le tampon d'une carte réseau.) En tant que protocole générique, PF_PACKET est traité ici et prioritairement par la fonction packet_rcv()… qui duplique le paquet (si un filtre ne le supprime pas avant) afin qu'il soit aussi traité par le "vrai" gestionnaire du protocole réseau de niveau 3 (majoritairement IP donc ip_rcv() ‒ dont la fin d'exécution déclenche le prerouting de Netfilter ‒). Source 1. Source 2.
  • Association de la socket à une interface réseau (bind()) ;

  • Activation éventuelle du mode promiscuous. tcpdump -p permet de ne pas l'activer ;

  • Application d'un éventuel filtre BPF/LSF (kernel-land) sur la socket. tcpdump -d affiche le bytecode BPF ;

  • Activation d'un espace mémoire partagé kernel/user-land afin d'éviter des copies inutiles du paquet. Détails ;

  • Lire les paquets reçus.

Redémarrer automatiquement un démon sysvinit crashé avec systemd

Depuis quelques mois, nous avons un serveur coturn installé depuis les dépôts Debian GNU/Linux. Implémentation de TURN, l'un des nombreux palliatifs pour tenter de faire fonctionner des logiciels multimédias pair-à-pair avec du NAT au milieu.

Ce service se vautre plusieurs fois par semaine. Évidemment, rien dans son journal. Sauf aujourd'hui : segfault. Mes recherches sur le web retournent rien (ou des paths vieux de 5-7 ans, qui ont donc été intégré depuis, en ce qui concerne l'erreur de segmentation). On pourrait lancer coturn en mode ultra-verbeux (« -V »), sortir gdb (pour la segfault), etc. Mais en attendant ?

Pour ces cas-là, systemd propose Restart=on-failure, c'est-à-dire redémarrer un service à chaque fois qu'il crashe.

J'utilise ça dans des units systemd depuis plus de 5 ans. Mais, dans Debian, coturn est livré avec un script sysv qui utilise start-stop-daemon. Est-ce que ça fonctionne ? Oui.

Utiliser la commande systemctl edit coturn.service.

Saisir :

[Service]
RemainAfterExit=no
Restart=on-failure
RestartSec=10s

Enregistrer.

Désormais, systemctl status coturn affiche deux lignes supplémentaires : « Drop-In: /etc/systemd/system/coturn.service.d / override.conf ».

« RemainAfterExit » est nécessaire, sinon systemd ne fait pas le job.

« RestartSec » n'est pas nécessaire mais laisse un peu de répit à coturn pour démarrer et éviter une boucle de redémarrage (systemd essaye de faire redémarrer un service en boucle, jusqu'à une limite pré-définie et surchargeable, après quoi il laisse le service HS, ce que je ne veux pas).

Si l'on veut tester, on remplace « on-failure » par « always » puis killall coturn.

Comment le mode promiscuous d'une carte réseau est activé par Linux ?

Après avoir voulu me remémorer comment fonctionne une capture réseau du point de vue du noyau Linux, je me suis interrogé sur l'activation, en détail, du mode promiscuous.

Définition

Une carte réseau Ethernet fait remonter au système d'exploitation uniquement les trames Ethernet qui lui sont destinées. Le tri est opéré sur l'adresse MAC de destination. Sont conservés les paquets dont l'adresse MAC est celle de la carte réseau, l'adresse broadcast (FF:FF:FF:FF:FF:FF), ou l'une des adresses multicast (01:00:5E:XX:XX:XX pour IPv4, 33:33:XX:XX:XX:XX pour IPv6).

Ce filtrage est effectué matériellement par la carte réseau.

Le mode promiscuous permet de retirer ce filtrage, donc d'écouter tout ce qui passe sur le réseau.

Bon, il faut relativiser :

  • À l'époque des réseaux équipés avec des hubs ou avec une topologie en bus, on recevait tout le trafic réseau de toutes les machines du réseau.

  • Sur les réseaux d'aujourd'hui, équipés avec des switchs, on reçoit uniquement le trafic destiné à la machine, broadcast, multicast ou autre (01:80:C2:XX:XX:FF pour LLDP / STP / IEEE1905, etc.). Le seul moment où un switch fait fuiter du trafic unicast, c'est quand il ne connaît pas l'association entre une adresse MAC et un port ou quand sa table de correspondance est pleine.


Pratique

Le mode promiscuous est global à une interface réseau : exécuter un tcpdump -p (on demande à ne pas activer le mode promiscuous) à côté d'un tcpdump (mode promiscuous activé) est vain : quel que soit l'ordre de lancement des commandes, le tcpdump -p verra les paquets qui ne sont pas destinés à la machine tant que le tcpdump (promiscuous) est en cours d'exécution.

Voir si l'une des interfaces réseau du système est en mode promiscuous : ip -d l sh | grep -B 1 promiscuity. Si le nombre affiché est supérieur à 1, alors plusieurs applications (+ le noyau voir ligne suivante) ont activé le mode promiscuous.

Activer le mode promiscuous sans passer par libpcap : sudo ip l set promisc on dev <INTERFACE>. Pour le désactiver : sudo ip l set promisc off dev <INTERFACE>.


Promiscuous ne voit pas tout

Le mode promiscuous ne permet pas de voir TOUT le trafic réseau adressé à la machine. Une carte réseau peut filtrer matériellement le trafic qu'elle juge invalide : toute trame 802.1Q quand aucun VLAN est configuré sur le système, trames réseaux trop courtes ou dont la somme de contrôle est incorrecte, etc.


Linux filtre aussi

Tous les paquets qui arrivent sur une interface configurée en mode promiscuous ne parviendront pas jusqu'aux logiciels "serveurs" en écoute, car le noyau fait le tri.

Le tri le plus simple à mettre en évidence est que, si le transfert de paquets IP n'est pas activé (/proc/sys/net/ipv4/ip_forward = 0), alors tous les paquets IP dont l'adresse IP de destination n'est pas l'une des IPs de la machine (multicast inclus) seront détruits. Ils n'arriveront même pas jusqu'à la chaîne FORWARD de Netfilter/iptables. Il doit exister d'autres filtrages.


Et donc, comment le mode promiscuous est activé par la libpcap ?

Si l'on regarde le code, la libpcap (utilisée par tcpdump, wireshark, etc.) utilise la fonction système setsockopt() pour positionner l'option. Il ne reste plus qu'à remonter le code de Linux.

  • setsockopt() est une fonction implémentée pour chaque famille/protocole. Une socket libpcap est de type AF_PACKET / PF_PACKET, donc c'est la fonction packet_setsockopt() située dans le fichier net/packet/af_packet.c qui est utilisée. Elle appelle packet_mc_add() qui appelle packet_dev_mc(), toujours dans af_packet.c.
    • dev_set_promiscuity() dans net/core/dev.c est appelée et elle appelle __dev_set_promiscuity() toujours dans net/core/dev.c. On notera que c'est cette fonction qui journalise « device XXXX entered promiscuous mode » dans kern.log.
      • __dev_set_promiscuity() appelle dev_change_rx_flags() (toujours dans net/core/dev.c) qui appelle ndo_change_rx_flags. Le pilote peut implémenter une fonction (ou non) pour remplacer celle par défaut et l'enregistrer dans la structure netdev_ops de la structure net_device. Exemple : le pilote e1000e n'implémente pas cette fonction.

      • __dev_set_promiscuity() appelle dev_set_rx_mode() (toujours dans net/core/dev.c) qui appelle ndo_set_rx_mode(). Le pilote e1000e la remplace avec e1000e_set_rx_mode() (« .ndo_set_rx_mode = e1000e_set_rx_mode, ») dont le commentaire indique « e1000e_set_rx_mode - secondary unicast, Multicast and Promiscuous mode ». Cette fonction met à jour les registres de la carte réseau et, surtout, restaure+écrit les adresses multicast et unicast quand on sort du mode promiscuous.

Interface FOG lente lors d'un déploiement ? « Storage node » rémanent ?

Quand on crée une tâche de déploiement d'une image disque (sur une seule machine, en unicast, ou sur une partie du parc, en multicast, même combat), l'interface web de notre serveur FOG (Free and Opensource Ghost), met énormément de temps (> 30 secs) à répondre pour annoncer que la tâche est créée.

Si l'on n'attend pas, que l'on va dans l'onglet « Tasks », on voit que la tâche est créée.

Origine du problème : un « storage node » apparaît toujours dans la liste alors qu'il n'existe plus. L'image disque que l'on veut déployer ne dépend pas de ce serveur, mais sa suppression depuis l'interface web de FOG résout la lenteur de celle-ci.

Commande mysql sur un serveur avec plusieurs instances MariaDB : préciser le port avec « -P » ne suffit pas pour se connecter à la bonne instance

Nous avons un serveur avec plusieurs instances de MariaDB. Une instance = un processus, un port TCP, un fichier de configuration dédié, etc.

Sur ce serveur, si l'on tape la commande mysql -P <numéro_port_instance> -u <user> -p <nom_bdd>, MariaDB nous retourne l'erreur « ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 "No such file or directory" ».

Si l'instance par défaut (celle qui écoute sur tcp/3306 / /var/run/mysqld/mysqld.sock) est toujours active, l'erreur est, en toute logique, « ERROR 1045 (28000): Access denied for user `'@'localhost' (using password: YES) », car on tente de se connecter à l'instance par défaut.

Le paramètre « -P », qui permet de préciser le port TCP à utiliser, ne se suffit pas à lui-même : il faut ajouter --protocol=tcp.

Ou utiliser la socket UNIX de l'instance à laquelle on veut accéder avec -S /var/run/mysqld/<socket>.

Ubuntu GNU/Linux : ajouter des utilisateurs LDAP / Samba 4 AD DC à un groupe local

Sur notre parc Ubuntu GNU/Linux, l'authentification des utilisateurs pour l'ouverture de session se fait auprès de notre domaine Samba 4 AD DC (et sur un OpenLDAP auparavant).

On voudrait ajouter tous les utilisateurs du domaine à des groupes locaux (qui n'existent pas dans notre domaine). On ne peut donc pas utiliser /etc/group puisque l'utilisateur est enregistré dans une autre base de données.

Quel intérêt de faire ça ? Permettre à nos utilisateurs d'utiliser des logiciels qui réclament l'appartenance à un groupe local. Exemple : il faut être membre du groupe « vboxusers » pour avoir le droit de connecter des périphériques USB (clé USB, webcam, etc.) à une machine virtuelle VirtualBox. On n'a pas envie de pourrir notre domaine avec des goupes, sans compter que VirtualBox réclame une appartenance au groupe « vboxusers », pas au groupe « vboxusers@domaine.monorganisation.example »).

Comme d'hab, PAM vient à notre rescousse avec son module pam_group.

D'abord, on ajoute une ligne *;*;*;Al0000-2400;vboxusers dans le fichier /etc/security/group.conf. On peut restreindre l'application de la règle à certains services PAM (sshd, par exemple), c'est le premier champ. Ou à certains terminaux (deuxième champ). Ou à certains utilisateurs / groupes d'utilisateurs (troisième champ). Que à certains horaires (quatrième champ), ici tous les jours (« Al »), de 0 h à 24 h (0000-2400).

Ensuite, il faut ajouter pam_group à la liste des modules PAM exécutés sur le système. Par défaut, il est appelé depuis le fichier /etc/pam.d/login donc l'ajout de groupes fonctionne depuis les tty (ctrl+alt+Fx), mais pas sur une connexion SSH ou depuis un émulateur de terminaux lancé depuis l'interface graphique (gnome-terminal, etc.). Pour corriger cela, j'ajoute une ligne auth optional pam_group.so à la fin de /etc/pam.d/common-auth. Attention : si tu utilises pam_script, qui permet de lancer un script lors de l'authentification, de l'ouverture / fermeture de session, il faut que pam_group soit appelé avant pam_script sinon l'utilisateur ne sera pas ajouté aux groupes.

Il faut redémarrer le système pour que la modification devienne effective. Fermer la session ne suffit pas. Redémarrer le gestionnaire d'affichage (gdm3, dans mon cas) ne suffit pas.

Note : un bug dans systemd empêchait l'ajout de groupes par pam_group lors d'une ouverture de session graphique. Il est corrigé depuis longtemps, y compris dans Ubuntu 20.04.

Merci Alex d'avoir mis ça en prod'.

-