đ 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.service
etNetworkManager-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 :
- 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
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.
-
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.service
correspond Ă 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}.target
correspond Ă 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
.service
et un fichier.socket
. - Le fichier
.socket
doit ĂȘ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â.