🌐 Des dĂ©clenchement selon l'Ă©tat du rĂ©seau

| ~ 9 mins | 1778 mots

Introduction

Dans un prĂ©cĂ©dent article, j’ai prĂ©sentĂ© le fait qu’il est possible sans grande difficultĂ© de dĂ©marrer des services systemd avec des rĂšgles udev, ce qui est trĂšs pratique pour dĂ©tecter le branchement d’un disque dur ou d’une carte sd.

Mais qu’en est-il quand on veut faire des actions liĂ©es au rĂ©seau ?

Cas 1 : ÉvĂ©nement sur l’existence de l’interface rĂ©seau

Le point le plus simple et le plus similaire au cas prĂ©cĂ©dent est de rĂ©aliser une action sur le “branchement” d’une interface rĂ©seau. Cela peut avoir du sens dans certains cas particulier tel que le partage de connection usb depuis un tĂ©lĂ©phone : L’interface n’existe que si le tĂ©lĂ©phone active l’option et disparaĂźt Ă  l’instant oĂč le partage se termine.

Dans ce cas prĂ©sent, la mĂ©thode la plus fiable est de jouer lĂ  aussi avec les rĂšgles udev. Mais si vous pouvez garantir la stabilitĂ© du nom de l’interface, par exemple avec networkd, une mĂ©thode plus simple existe :

En effet, le systĂšme prĂ©sente les interfaces rĂ©seau sous la forme de device systemd. Il est ainsi possible d’utiliser ces Ă©lĂ©ments pour exĂ©cuter un service

Pour voir les interfaces rĂ©seaux dĂ©couvertes sur votre machine par systemd :

systemctl | grep sys-subsystem-net-devices

Voyons donc comment faire dans un cas concrĂȘt

Forcer un nom d’interface stable avec networkd

Par dĂ©fault sur ma machine, le nom des interfaces usb de tethering sont systĂ©matiquement modifiĂ© Ă  la reconnection ce qui n’est pas pratique dans notre cas.

Pour ce faire, je vais utiliser networkd car la configuration de celui-ci est trùs simple. Il est possible de forcer un nom d’interface stable avec networkd sans passer par de la configuration udev.

Petit apartĂ© sur la cohabitation systemd-networkd / NetworkManager: Contrairement Ă  ce que peut faire croire certaines documentations, il est aujourd’hui tout Ă  fait possible de faire tourner Ă  la fois network-manager et network-manager sur la mĂȘme machine dans le mĂȘme temps. Il faut juste clarifier quelle interface vous gĂ©rer avec quel outil. Network-manager excelle pour les configurations dynamiques (connection wifi par exemple), lĂ  oĂč systemd-networkd est idĂ©al pour des interfaces statiques. Cet Ă©tat de gestion partiel par un outil abouti Ă  ce que l’outil obtienne une vision rĂ©duite sur l’interface, mais il reste nĂ©anmoins conscient de leur existence. De ma connaissance le seul conflit qu’il peut ĂȘtre nĂ©cessaire de rĂ©soudre est d’éviter d’avoir Ă  la fois systemd-networkd-wait-online.service et NetworkManager-wait-online.service actif en mĂȘme temps.

Dans le cas prĂ©sent, on fixe vis Ă  vis du port usb car c’est plus simple, mais il est probablement possible de fixer cela mieux, nĂ©anmoins je manque de meilleures idĂ©es.

Pour cela, il faut Ă©videmment que networkd soit activĂ©, comme dit prĂ©cĂ©demment, ce n’est pas un gros soucis (du moins sous debian) mĂȘme si vous utilisez dĂ©jĂ  NetworkNanager:

/etc/systemd/network/10-netusb.link:

[Match]
# À ajuster avec le path obtenu pour l'interface avec udevadm info
# udevadm info /sys/class/net/enx02e17d08a1ad
Path=pci-*** 

[Link]
Name=netusb

/etc/systemd/network/10-netusb.network:

[Match]
Name=netusb

[Network]
DHCP=yes

Relancez networkd:

sudo systemctl daemon-reload && sudo systemctl restart systemd-networkd

Maintenant testons notre service

$HOME/.config/systemd/user/test_network.service:

[Unit]
Description=test_network
After=sys-subsystem-net-devices-netusb.device
BindsTo=sys-subsystem-net-devices-netusb.device

[Service]
Type=simple
ExecStart=echo "running"
RemainAfterExit=true

[Install]
WantedBy=sys-subsystem-net-devices-netusb.device

Il vous faut l’activez:

systemctl --user enable test_network

Maintenant si vous activez le partage rĂ©seau sur le tĂ©lĂ©phone le service dĂ©marrera. Il vivra mĂȘme le temps de l’interface rĂ©seau grĂące Ă  BindTo et RemainAfterExit.

Cas 2 : ÉvĂ©nement sur l’état du rĂ©seau / de l’interface rĂ©seau

Pouvoir rĂ©agir sur l’existence d’une interface rĂ©seau, c’est bien, mais pouvoir rĂ©agir sur l’état d’une interface rĂ©seau ça semble nĂ©anmoins un cas plus pertinent : DĂ©marrer son service quand le rĂ©seau est actif, redĂ©marrer un service si le rĂ©seau “saute”, lancer un script de backup local quand le rĂ©seau se dĂ©connecte, etc.

network-online.target

La premiĂšre intuition serait d’utiliser ce qui existe dĂšs Ă  prĂ©sent dans systemd, la target “network-online.target”. Malheureusement, elle n’est utile que pour le premier accĂšs au rĂ©seau de la machine :

Note that this unit is only useful during the original system start-up logic. After the system has completed booting up, it will not track the online state of the system anymore. Due to this it cannot be used as a network connection monitor concept, it is purely a one-time system start-up concept.

Cela peut ĂȘtre utile dans de nombreux cas cependant, mais ne couvre pas tous les usages.

Script “Dispatcher”

Faute de solution systemd qui semble purement intégré et standard, il faut donc soit :

Il existe quelques logiciels existant pour gĂ©rer ce genre de cas :

  1. NetworkManager possĂšde mĂ©canisme de script dit “dispatcher” qui semble plutĂŽt Ă©prouvĂ©.
  2. Systemd-networkd ne possĂšde actuellement (aoĂ»t 2025) pas de solution, mais des logiciels tiers comparables au dispatcher de NetworkManager existe :
    • networkd-dispatcher qui semble Ă  priori un bon choix avec sa prĂ©sence dans les dĂ©pĂŽts, dans les fait le logiciel s’avĂšre pour le moment dĂ©cevant.
      • La gestion des interfaces qui change de nom est dysfonctionnelle.
      • Contrairement Ă  NetworkManager, il semblerait qu’il soit obligatoire de placer les scripts dans des dossiers par Ă©tat.
    • network-broker : non testĂ©, en rust, mais qui semble basĂ© sur la mĂȘme logique que networkd-dispatcher donc probablement les mĂȘmes problĂšmes, pas beaucoup de dĂ©veloppeurs.
    • network-event-broker : Non testĂ© , en go, créé par vmware.

Dans tous les cas, il s’agit lĂ  de script bash pour gĂ©rer le rĂ©seau. Ce n’est pas le plus Ă©lĂ©gant, mais il est Ă©videmment envisageable de convertir ces scripts pour lancer/arrĂȘter des target/service systemd correspondant Ă  nos besoins.

Exemple: NetworkManagers Dispatcher

Un petit exemple pour forcer un logiciel Ă  se redĂ©marrer si une nouvelle connexion gĂ©rĂ©e par NetworkManager est “UP” :

/etc/NetworkManager/dispatcher.d/01-restart-myservice-on-new-network-up :

#!/bin/bash
case $2 in
  up)
    systemctl try-restart --no-block myservice.service
  ;;
  *)
    ;;
esac
exit 0

La solution idéale ?

La solution qui me paraĂźtrait idĂ©al pour des cas simple, serait tout simplement d’avoir un service :

Une implémentation assez simple de ma part se trouve ici En démarrant le script python listen_events.py via le service networkd_listener.service (il vous faut dbus-fast),

les targets suivantes sont gĂ©rĂ©s :

Avec une telle solution, on ne peut pas forcément gérer des cas fin de réseaux, mais on peut gérer relativement simplement le cas qui seront probablement le plus commun :

DĂ©marrer/arrĂȘter des services selon si le rĂ©seau est actif ou nom, gĂ©nĂ©ralement ou pour une interface spĂ©cifique.

On peut imaginer par exemple une autre version du service test_network qui ne fonctionne que si l’interface usb est fonctionnelle (et non existante comme dans l’exemple prĂ©cĂ©dĂ©nt) :

[Unit]
Description=test_network
After=networkd_link_status@netusb.target
BindsTo=networkd_link_status@netusb.target

[Service]
Type=simple
ExecStart=echo "running"
RemainAfterExit=true

[Install]
WantedBy=networkd_link_status@netusb.target

Et Si l’interface rĂ©seau n’importe pas :

[Unit]
Description=test_network
After=networkd_status.target
BindsTo=networkd_status.target

[Service]
Type=simple
ExecStart=echo "running"
RemainAfterExit=true

[Install]
WantedBy=networkd_status.target

Cas 3 : ÉvĂšnements sur la connectivitĂ© Ă  l’internet

La version prĂ©cĂ©dente, permet de faire fonctionner un service si le rĂ©seau existe (= possĂšde une adresse) mais ne garantie pas de l’accĂšs internet est bel et bien prĂ©sent.

On peut imaginer une solution simple pour ce cas :

ping_test.py:

#!/usr/bin/env python3

import os
import random
import subprocess
import time
import datetime

def test_targets(targets, count) -> bool:
    """
    Tests connection to target, return true if one of the target is reachable
    """
    for target in targets:
        ping_process = subprocess.run(["ping", "-c", f"{str(count)}", target])
        if not ping_process.returncode:
            return True
    return False

ping_targets = os.environ.get("PING_TARGETS", "")
if not ping_targets:
    exit(1)
ping_targets = ping_targets.split(",")
heartbeat = int(os.environ.get("HEARTBEAT", "60"))
ping_count =  int(os.environ.get("PING_COUNT", "1"))

while True:
    random.shuffle(ping_targets)
    if not test_targets(ping_targets, ping_count):
        print(f"{datetime.datetime.now()} - HEARTBEAT FAILED")
        exit(0)
    print(f"{datetime.datetime.now()} - HEARTBEAT SUCCESS")
    time.sleep(heartbeat)

wan.service:

[Unit]
Description=Internet working ?
After=networkd_status.target
BindsTo=networkd_status.target

[Service]
Type=simple
# check with multiple url in case, one of them fail for internal reason
Environment=PING_TARGETS=inkey-art.net,perdu.com,perdus.com,linuxfr.org
# heartbeat in sec (verify connection each 2min)
Environment=HEARTBEAT=120
ExecStart=/opt/ping_test.py
Restart=always

[Install]
WantedBy=networkd_status.target

On peut d’ailleurs comme dans l’exemple ici profiter de l’existence de networkd_status.target pour Ă©viter de ping quand le rĂ©seau est assurĂ©ment non disponible.

Cas 4 : Évùnement sur l’activation d’un socket.

Il reste un dernier cas, un peu diffĂ©rent, mais il me fallait l’aborder, d’autant qu’il est trĂšs bien intĂ©grĂ© Ă  systemd. C’est l’activation par socket.

L’idĂ©e n’est lĂ  pas tant d’agir sur le rĂ©seau au sens interface rĂ©seau, mais plutĂŽt au sens connection, au sens socket.

Le principe de l’activation par socket est de pouvoir dĂ©marrer un service que si une connection au socket associĂ© est ouverte.

J’y vois 2 grand intĂ©rĂȘt :

Ce cas est dĂ©jĂ  bien prĂ©sentĂ© dans le blog d’ilManzo’s en anglais. Je vous conseille donc d’y jeter un Ɠil.

Pour rĂ©sumer rapidement la procĂ©dure :

Conclusion

Creuser cette question m’a beaucoup amusĂ©.

J’ai Ă©tĂ© un peu déçu du manque de complĂ©tion de systemd sur la partie Ă©vĂ©nement liĂ© au rĂ©seau, mais comme on le voit, il est possible assez facilement d’y remĂ©dier. On peut espĂ©rer nĂ©anmoins Ă  l’avenir quelques mĂ©canismes un peu plus solides et intĂ©grer pour ce type d’évĂšnement dans systemd. Le cas de vouloir dĂ©marrer un service Ă  la connection/dĂ©connexion d’une interface rĂ©seau ne me semble pas si extravagant que systemd ne mĂ©riterait une intĂ©gration de ce nom.

En attendant, il est possible de bricoler des targets et du service systemd pour un peu tout ce qu’on veut et ainsi avoir un systĂšme plus “rĂ©actif”.