đ Des dĂ©clenchement selon l'Ă©tat du rĂ©seau
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 foissystemd-networkd-wait-online.serviceetNetworkManager-wait-online.serviceactif 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 :
- nous tourner vers des solutions dĂ©pendantes de nos outils rĂ©seaux : NetworkManager, systemd-networkd, âŠ
- crĂ©er nous mĂȘme notre solution logicielle pour rĂ©pondre Ă ce problĂšme.
Il existe quelques logiciels existant pour gérer ce genre de cas :
- NetworkManager possĂšde mĂ©canisme de script dit “dispatcher” qui semble plutĂŽt Ă©prouvĂ©.
- Systemd-networkd ne possÚde actuellement (août 2025) pas de solution, mais des logiciels tiers comparables au
dispatcherde 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.
- 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.
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 :
- capable d’Ă©couter en dbus sur les Ă©vĂšnements networkd.
- capable via ces donnĂ©es d’activer/dĂ©sactiver des target systemd correspond au fait que l’interface rĂ©seau fonctionne.
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 :
networkd_status.servicecorrespond Ă l’Ă©tat gĂ©nĂ©ral de la connectivitĂ© de la machine (faire partie d’un rĂ©seau). le service est actif si l’ “OperationalState” gĂ©nĂ©ral est “routable”.networkd_link_status@[interface_name}.targetcorrespond Ă l’Ă©tat de la connectivitĂ© d’une des interfaces. le service est actif si l’ “OperationalState” de l’interface est “routable”.
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 :
- un script python utilisant ping testant de temps en temps la connectivité
- un service systemd qui utilise ce script.
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 :
- rĂ©duire la latence du boot (pas de nĂ©cessitĂ© de dĂ©marrer un dĂ©mon ssh si le port ssh n’est pas contactĂ©)
- rĂ©duire l’usage des services utilisĂ© peu rĂ©guliĂšrement. Je pense que la mĂ©moire vive est probablement lĂ oĂč ça doit jouer le plus.
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 :
- Il vous faut un fichier
.serviceet un fichier.socket. - Le fichier
.socketdoit ĂȘtre activĂ© et dĂ©marrĂ© pour Ă©couter sur le port voulu. - Il faut adapter le code du serveur pour qu’il puisse utiliser le socket dĂ©jĂ ouvert.
- Il est possible via diverses astuces notamment systemd-socket-proxyd de gĂ©rer le service de sorte Ă qu’il s’arrĂȘte s’il n’y a plus d’activitĂ© sur le socket.
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”.