Piloter Marionnet par un script ou par une IA
Ce texte fait foi ; la version anglaise en est la traduction.
Depuis 2026, Marionnet peut recevoir ses ordres d’un programme, et non plus seulement de la souris. Un script peut ainsi construire le réseau d’un TP, le démarrer, attendre que les machines soient prêtes, leur faire exécuter des commandes, puis vérifier que le réseau est bien dans l’état voulu ; un agent conversationnel, c’est-à-dire une IA capable d’exécuter des commandes (un assistant de programmation en ligne de commande, par exemple), peut en faire autant à partir d’un énoncé rédigé en langage naturel. Notons que Marionnet n’embarque aucune IA : c’est la vôtre qui le pilote. Pendant tout ce temps, la fenêtre de Marionnet reste à l’écran : vous voyez le réseau se construire et les machines démarrer, et vous pouvez même intervenir à la souris en même temps que le programme.
Quelques usages, pour fixer les idées :
- préparer le projet d’un TP sans cliquer, puis le reconstruire à l’identique d’une année sur l’autre, ou en plusieurs variantes ;
- livrer aux étudiants un projet qui démarre tout seul ;
- vérifier en une commande qu’un réseau répond à l’énoncé (adresses, routes, connectivité), plutôt que d’ouvrir chaque machine l’une après l’autre ;
- faire construire, puis vérifier un TP par une IA, qui pilote Marionnet à votre place.
Ce guide présente ce mode de fonctionnement du point de vue de l’utilisateur. Les détails techniques, qui ne servent que dans des cas particuliers, sont repliés dans des encadrés qui s’ouvrent d’un clic.
1. Lancer Marionnet en mode piloté
Il suffit d’ajouter une option au lancement de Marionnet :
marionnet --control-socket /tmp/marionnet.sock
Le fichier /tmp/marionnet.sock est le point de rendez-vous entre Marionnet et le programme qui le
pilote (techniquement, une socket Unix, autrement dit un fichier spécial par lequel deux
programmes d’une même machine se parlent). Sans cette option, ce point de rendez-vous n’existe pas,
et Marionnet ne peut donc pas être piloté : c’est vous qui décidez, à chaque lancement, s’il accepte
des ordres venus d’ailleurs.
Les ordres s’envoient ensuite par mrnctl, le pilote, installé avec Marionnet (son nom complet
est marionnet-ctl), auquel on indique une fois pour toutes le point de rendez-vous :
export MARIONNET_CONTROL_SOCKET=/tmp/marionnet.sock
mrnctl new /tmp/tp1.mar
mrnctl add machine m1
mrnctl start m1
Chaque ligne est un ordre, que Marionnet exécute comme s’il venait de la souris : la machine m1
apparaît sur le plan du réseau, puis elle démarre, et son terminal s’ouvre.
Le choix du chemin, et ce qu'il faut installer
Le chemin doit être absolu, et ne pas dépasser 107 octets (c’est une limite du système, et non de Marionnet). S’il est relatif, trop long, situé dans un répertoire où Marionnet ne peut pas écrire, ou déjà utilisé par un autre Marionnet en cours d’exécution, Marionnet refuse de démarrer et en donne la raison : un script échoue ainsi dès sa première ligne, plutôt que dix ordres plus loin.
Le pilote cherche le point de rendez-vous dans son option --socket=CHEMIN, à défaut dans la
variable MARIONNET_CONTROL_SOCKET, et nulle part ailleurs : il ne part jamais à la recherche d’un
Marionnet. Il a besoin du programme socat, et de jq pour ses options de mise en forme
(--pretty, --query), deux programmes que les paquets de Marionnet installent avec lui.
Plusieurs pilotes peuvent se connecter au même Marionnet, huit au plus en même temps.
La touche TAB complète les ordres, leurs options, et surtout les noms réels du projet ouvert (composants, ports, champs), qu’elle demande au Marionnet en cours d’exécution :
$ mrnctl connect c2 s1:<TAB>
s1:port1 s1:port2 s1:port3 s1:port4
Si vous avez installé Marionnet par un paquet (.deb ou .rpm) ou par son programme
d’installation, vous n’avez rien à faire pour en profiter : la complétion est installée avec le
logiciel, et le terminal la charge de lui-même, dans tout terminal ouvert après l’installation.
Si la touche TAB ne complète rien
La complétion est chargée par le paquet bash-completion de votre distribution. Ce paquet est
présent sur la plupart des postes de bureau, mais pas toujours sur une installation minimale ; au
besoin, installez-le, puis ouvrez un nouveau terminal :
# Debian, Ubuntu :
sudo apt install bash-completion
# Fedora, Rocky Linux, AlmaLinux :
sudo dnf install bash-completion
# openSUSE :
sudo zypper install bash-completion
Si vous utilisez Marionnet depuis ses sources, sans l’avoir installé, chargez la complétion à la
main dans chaque terminal (ou une fois pour toutes dans votre ~/.bashrc), depuis la racine des
sources :
. bin/scripts/marionnet-completion.bash
Quand aucun Marionnet ne tourne, la complétion des ordres et des options peut s’appuyer sur un instantané du vocabulaire, pris une fois pour toutes (les noms du projet, eux, ne peuvent alors pas être complétés) :
mrnctl help > ~/.marionnet-grammar.json
export MARIONNET_CTL_GRAMMAR=~/.marionnet-grammar.json
Seul ou à deux : qui tient les commandes ?
Marionnet ouvre parfois de lui-même une fenêtre : la page de garde au démarrage, un avertissement, le récapitulatif des adaptations faites à un ancien projet, une erreur. Ce qu’il convient de faire de ces fenêtres dépend de qui se trouve devant l’écran, et c’est la première question à vous poser.
-
Le programme pilote seul. Personne n’est là pour lire ces fenêtres, ni pour les fermer, et une fenêtre qui attend une réponse bloquerait tout. Marionnet les affiche donc deux secondes, puis les ferme de lui-même, en conservant ce qu’elles disaient, que le programme relit par
mrnctl notifications. C’est le comportement par défaut : il n’y a rien à ajouter à la commande de lancement. -
Le programme et un humain pilotent ensemble. Vous construisez par exemple le réseau par un script, mais vous voulez ensuite cliquer vous-même pour l’explorer ; ou bien vous montrez Marionnet à des étudiants pendant qu’un script le fait travailler. Il faut alors que les fenêtres restent à l’écran, comme d’habitude, et c’est le rôle de l’option
--keep-dialogs:marionnet --control-socket /tmp/marionnet.sock --keep-dialogsC’est donc l’humain qui lit et ferme les fenêtres, et qui répond aux questions que pose Marionnet, y compris quand un ordre du programme les a provoquées ; le programme attend alors sa réponse. En contrepartie,
mrnctl notificationsne recueille plus rien.
Dans les deux cas, l’humain garde la main : rien ne l’empêche de cliquer pendant qu’un programme pilote, et Marionnet ne répond jamais à sa place à une question qu’il a lui-même déclenchée.
Le cas d’une IA se ramène à l’un des deux précédents, selon la façon dont elle agit. Une IA qui
exécute des commandes envoie ses ordres par mrnctl, exactement comme un script : elle prend la
place du programme, et vous choisissez de la laisser piloter seule ou de piloter avec elle. Mais une
IA capable de voir l’écran et de cliquer peut aussi prendre la place de l’humain, et cela sans
aucune option, puisque Marionnet la traite alors comme un utilisateur ordinaire. Pour apprendre
à une IA à piloter Marionnet, donnez-lui à lire ../marionnet-driving-skill.md, qui a été écrit à son
intention : il lui explique comment construire, jouer et vérifier un TP par des commandes. La
conception du TP n’est pas son objet, même si l’IA peut bien sûr vous y aider par ailleurs.
Régler le délai de fermeture des fenêtres
Quand le programme pilote seul, l’option --dialog-timeout=N change le temps pendant lequel une
fenêtre reste affichée avant de se fermer ; N s’exprime en millisecondes, et vaut 2000 par
défaut. Ce délai n’est pas nul, afin qu’une personne qui regarde l’écran (ou une copie d’écran)
voie encore ce qui s’est passé.
2. Un premier réseau piloté
Voici, en entier, un réseau semblable à celui de la page Premiers pas (deux machines reliées par un switch), construit, démarré et enregistré cette fois par un script :
export MARIONNET_CONTROL_SOCKET=/tmp/marionnet.sock
# Lancer Marionnet, puis attendre qu'il réponde :
marionnet --control-socket "$MARIONNET_CONTROL_SOCKET" &
until mrnctl status > /dev/null 2>&1; do sleep 0.5; done
# D'abord un projet, comme à la souris :
mrnctl new /tmp/demo.mar
# Les composants, puis les câbles et leurs deux extrémités :
mrnctl add machine m1
mrnctl add machine m2
mrnctl add switch s1 --ports=4
mrnctl connect c1 m1:eth0 s1:port1
mrnctl connect c2 m2:eth0 s1:port2
# Les adresses :
mrnctl ifconfig-set m1 eth0 ipv4-address 10.0.0.1/24
mrnctl ifconfig-set m2 eth0 ipv4-address 10.0.0.2/24
# Démarrer, puis attendre que les machines tournent (cf. § 4) :
mrnctl start m1
mrnctl start m2
mrnctl wait m1 --state=on --timeout=120
mrnctl wait m2 --state=on --timeout=120
# Arrêter avant d'enregistrer (cf. § 5), puis quitter :
mrnctl stop m1
mrnctl stop m2
mrnctl wait-all --state=off --timeout=180
mrnctl save-as /tmp/demo.mar
mrnctl quit
On y retrouve, ligne à ligne, ce que l’on ferait à la souris. Les ports se désignent par leur nom,
comme dans l’interface : une machine numérote ses cartes réseau à partir de 0 (eth0, eth1), un
switch ses ports à partir de 1 (port1, port2). Une fois enregistré, ce projet se rejoue d’une
seule ligne, l’option -r démarrant aussitôt tous ses composants :
marionnet --control-socket /tmp/marionnet.sock -r /tmp/demo.mar
Le répertoire examples/ contient des scripts complets de ce genre, prêts à être exécutés.
3. Ce que répond Marionnet
Chaque ordre reçoit une réponse d’une ligne. Quand l’ordre est accepté, elle commence par
"ok":true ; quand il est refusé, par "ok":false, suivi d’un code et d’une explication en
clair, ici « “m1” cannot start from state “on” » (la machine m1 est déjà allumée) :
{"ok":false,"error":"forbidden_transition","detail":"…"}
Un script n’a d’ailleurs pas besoin de lire la réponse pour savoir si l’ordre a réussi : comme toute
commande Unix, mrnctl rend le code 0 en cas de succès, ce qui permet d’écrire
mrnctl start m1 && echo démarrée.
Deux règles simples évitent la plupart des refus.
-
Un programme ne peut faire que ce que permet l’interface graphique. Une machine allumée ne se supprime pas plus par un ordre que par la souris, et une machine déjà démarrée ne se démarre pas une seconde fois. Pour savoir ce qui est permis sur un composant à un instant donné, il suffit de le demander. Pour une machine éteinte, par exemple :
mrnctl can m1 # {"ok":true,"name":"m1","kind":"machine","state":"off", # "can":["set","del","start"],"beyond_gui":[]}Le champ
candonne les actions permises en ce moment : modifier la machine (set), la supprimer (del) ou la démarrer (start). Une fois allumée, elle ne pourra plus être supprimée, mais elle pourra être arrêtée (stop). Le champbeyond_guisignale, parmi les actions permises, celles qui n’ont pas de bouton dans l’interface, comme l’extinction brutale d’une seule machine (poweroff). Les câbles, eux, se modifient et se suppriment même quand le réseau fonctionne, comme dans l’interface graphique, et pour la même raison : on peut débrancher un câble réel et le rebrancher ailleurs sans rien éteindre. -
La liste des ordres se demande à Marionnet.
mrnctl helpdonne tous les ordres avec leur syntaxe, etmrnctl help connectcelle d’un seul. Cette liste s’enrichit d’une version à l’autre, de sorte que c’est la réponse du Marionnet que vous pilotez qui fait foi ; les deux encadrés qui suivent en donnent toutefois un aperçu.
Exemple : la liste des ordres de Marionnet 1.0.470 (novembre 2026)
Voici la syntaxe de chaque ordre, telle que l’a restituée, le 4 novembre 2026, la commande suivante :
mrnctl -q '.commands[].syntax' help
Les lignes trop longues ont été coupées pour tenir dans l’encadré, et les parties entre crochets sont facultatives. Cette liste ne dépend pas du projet ouvert : elle était identique sans projet et avec le projet de l’encadré suivant. Elle dépend en revanche de la version de Marionnet, et une version plus récente peut en offrir davantage.
help [<command>]
status
ls [--kind=<kind>] [--can=<action>]
can [<component>]
add <kind> <name> [--ports=<n>] [--<field>=<value>]…
del <component>
get <component> [<field>]
set <component> <field> <value>
rename <component> <new name>
connect <cable> <node>:<port> <node>:<port> [--crossover]
rc-get <component> [<field>|--field=<field>]
rc-set <component> [<one-line content>]
[--from=<absolute path>] [--enable|--disable]
[--select|--unselect] [--terminal|--no-terminal]
[--field=<field>]
ifconfig [<node>]
defects [<node>]
history [<node>]
documents
ifconfig-set <node> <port> <field> [<value>]
[--restart|--no-restart]
defects-set <node> <port> <inward|outward> <field> [<value>]
[--restart|--no-restart]
defects-set <cable> <leftward|rightward> <field> [<value>]
history-start <cow file>
history-del <cow file> [--except]
history-set <cow file> <field> [<value>]
history-export <cow file> <variant name> [--force]
open <absolute path>
new <absolute path> [--save|--no-save]
save
save-as <absolute path>
close [--save|--no-save]
notifications [--since=<n>] [--clear]
log <component> [<file>|--file=<file>] [--tail=<n>]
switch-info <switch> [<table>|--table=<table>]
report <component> [--timeout=<s>]
exec <component> <command-line> [--timeout=<s>]
wait <component> (--state=on|off|sleeping | --ready)
[--timeout=<s>]
wait-all --state=on|off|sleeping [--timeout=<s>]
quit
start <component>
stop <component>
suspend <component>
resume <component>
poweroff <component>
restart <component>
start-all
shutdown-all
poweroff-all
Exemple : ce qui est permis, dans un projet en marche
Ce que mrnctl can autorise dépend, au contraire, de l’état du projet à l’instant où on le
demande. Voici sa réponse, mise en tableau, pour un projet de trois machines reliées à un switch
par trois câbles, où m1, m2 et le switch s1 sont allumés, et m3 éteinte (Marionnet 1.0.470,
4 novembre 2026) :
| Composant | État | Actions permises |
|---|---|---|
m1, m2 (machines) |
allumées | stop, suspend, poweroff, restart |
m3 (machine) |
éteinte | set, del, start |
s1 (switch) |
allumé | stop, suspend, poweroff, restart |
c1, c2 (câbles de m1 et m2) |
branchés, en service | set, del, suspend |
c3 (câble de m3) |
branché, hors service | set, del, suspend |
On y lit qu’une machine allumée ne peut être ni modifiée ni supprimée, mais qu’elle peut être
arrêtée, suspendue, éteinte brutalement ou redémarrée ; les deux dernières actions sont marquées
beyond_gui, car l’interface graphique ne les propose pas pour une seule machine. Un câble, lui,
reste modifiable et supprimable quel que soit l’état du réseau, et suspend le débranche. Notons
enfin que le câble de m3 est hors service tant que m3 est éteinte.
Les codes de refus
Le champ error d’un refus est un code stable, sur lequel un script peut s’appuyer :
| Code | Signification |
|---|---|
unknown_command |
aucun ordre de ce nom (la réponse donne la liste de ceux qui existent) |
bad_argument |
nombre d’arguments erroné, option inconnue, valeur mal formée |
no_active_project |
aucun projet n’est ouvert : ouvrez-en un ou créez-en un d’abord |
unknown_node, unknown_port, unknown_field, unknown_direction, unknown_target, unknown_state, unknown_can |
ce qui est nommé n’existe pas |
forbidden_transition |
l’interface graphique l’interdirait aussi en ce moment (cf. mrnctl can) |
forbidden_in_exam_mode |
Marionnet a été lancé avec --exam, et l’ordre ferait perdre la copie de l’étudiant |
constraint_violated |
la valeur est refusée par le même contrôle que dans l’interface graphique |
restart_choice_required |
la modification exige de redémarrer un composant en marche : précisez --restart ou --no-restart |
unsaved_changes |
le projet a été modifié : précisez --save ou --no-save |
components_running |
un composant tourne, et le projet ne peut pas être enregistré dans cet état (cf. § 5) |
timeout |
le délai a expiré (la réponse dit ce qui a été observé en dernier) |
internal |
un bogue de Marionnet ; le détail contient ce qui a pu être capturé |
Les codes de retour du pilote, et la mise en forme des réponses
| Code | Signification |
|---|---|
0 |
Marionnet a accepté l’ordre |
1 |
Marionnet a répondu, et il a refusé |
2 |
l’erreur vient du pilote : pas de point de rendez-vous, outil manquant, option inconnue |
3 |
rien n’est revenu : Marionnet a disparu, il est bloqué, ou il n’a jamais été là |
La réponse est un objet JSON. Par défaut, mrnctl l’affiche telle quelle, sur une ligne ;
--pretty l’indente, et -q en extrait une partie par un filtre jq :
mrnctl --pretty status
# Le nom de chaque composant :
mrnctl -q '.nodes[].name' ls
# true si m1 peut être supprimée :
mrnctl -q '.can | index("del") != null' can m1
Un ordre est formé d’un verbe, d’arguments, puis d’options (--option=valeur). Les noms des
composants ne contenant jamais d’espace, seul le dernier argument d’un ordre qui prend un texte
libre (un commentaire, une commande) peut en contenir.
4. Attendre qu’une machine soit prête
Démarrer une machine, c’est démarrer un véritable noyau Linux, ce qui prend quelques secondes. Or
mrnctl start m1 répond tout de suite, car sa réponse signifie que l’ordre a été accepté, et non
que la machine a fini de démarrer. Un script qui enchaînerait sans attendre s’adresserait donc à une
machine qui n’est pas encore là. Il y a deux façons d’attendre, selon ce dont on a besoin.
-
Attendre que la machine tourne, c’est-à-dire que son processus existe :
mrnctl wait m1 --state=on --timeout=120L’attente est brève (moins d’une seconde), mais le système de la machine n’est pas encore utilisable : sur une Debian, il lui faut encore environ cinq secondes.
-
Attendre que la machine se dise prête. Marionnet ne peut pas savoir quand une machine est prête pour le TP, c’est-à-dire quand elle a fini ce qu’on attend d’elle (poser ses adresses, lancer ses services…) : seule sa configuration de démarrage le sait (cf. § 6). C’est donc à celle-ci de le signaler, par sa dernière ligne, en créant un fichier convenu (un fichier témoin) dans
/mnt/hostfs, un répertoire que Marionnet monte dans chaque machine et qu’il surveille depuis l’hôte :echo ready > /mnt/hostfs/marionnet-guest-readyDu côté du pilote, il ne reste alors qu’à attendre ce fichier témoin :
mrnctl wait m1 --ready --timeout=300Le nom du fichier doit être recopié exactement, puisque c’est lui que Marionnet guette. Il est en revanche inutile de l’effacer d’une séance à l’autre : Marionnet ne tient compte que du fichier écrit pendant le démarrage en cours. Enfin, une machine sans configuration de démarrage n’écrit jamais ce fichier, et
--readyattendrait donc jusqu’à l’expiration du délai : il faut alors s’en tenir à--state=on.
Transmettre un résultat dans le fichier témoin
Ce que la machine écrit dans le fichier revient dans la réponse de wait --ready. Au lieu du
simple mot ready, la configuration de démarrage peut donc y écrire un résultat, par exemple
celui d’un ping :
R="ping KO"
ping -c 3 10.0.0.2 > /dev/null && R="ping ok"
echo "$R" > /mnt/hostfs/marionnet-guest-ready
Le pilote le lit alors dans la réponse :
mrnctl -q .line wait m1 --ready --timeout=300
# ping ok
Enchaîner deux sessions : attendre la vraie fin de Marionnet
Si votre script quitte Marionnet pour en relancer un autre aussitôt, attendez que le premier soit
réellement terminé : deux sessions qui coexistent se disputent en effet les mêmes ressources de
l’hôte. Or mrnctl quit répond avant que Marionnet ait fini de s’arrêter (il faut bien que la
réponse parte avant l’arrêt), et Marionnet survit environ une demi-seconde à cette réponse, avec
ses machines.
Si c’est votre script qui a lancé Marionnet, comme au § 2, le plus simple est d’attendre la fin du
processus par la commande wait du shell, $! désignant le dernier processus lancé en
arrière-plan :
marionnet --control-socket "$MARIONNET_CONTROL_SOCKET" &
pid=$!
# … la session …
mrnctl quit
wait "$pid"
Si Marionnet a été lancé autrement (par vous, à la main, ou par un autre script), la réponse de
quit donne le numéro du processus, qu’il suffit de surveiller :
pid=$(mrnctl -q .pid quit)
while kill -0 "$pid" 2>/dev/null; do sleep 0.05; done
5. Construire et enregistrer un TP
Le § 2 a montré l’essentiel. Voici les ordres qui servent le plus souvent, avec leur équivalent à la souris :
| Ordre | Équivalent dans l’interface graphique |
|---|---|
new, open, save, save-as, close, quit |
les entrées du menu Projet |
add machine m1, add switch s1 --ports=8… |
Ajouter, dans la palette |
connect c1 m1:eth0 s1:port1 (--crossover pour un câble croisé) |
ajouter un câble |
set m1 memory 256, rename m1 pc1, del m1 |
Modifier, Supprimer |
start, stop, suspend, resume, start-all… |
les boutons de démarrage et d’arrêt |
ifconfig-set, defects-set |
une case des onglets Interfaces et Anomalies |
Deux précautions s’imposent, qui sont aussi celles de l’interface graphique.
- Marionnet ne perd jamais votre travail sans qu’on le lui dise. Si le projet en cours a été
modifié,
newetcloseattendent que vous précisiez--save(enregistrer d’abord) ou--no-save(abandonner les modifications), là où l’interface vous poserait la question. - Un réseau en marche ne s’enregistre pas, de même que l’interface grise ses entrées
d’enregistrement tant qu’une machine tourne. Arrêtez donc d’abord les machines, ou bien utilisez
close --save, qui arrête tout, attend que tout soit arrêté, puis enregistre (comptez alors quelques dizaines de secondes).
Ce que Marionnet choisit, accepte ou refuse quand vous ajoutez un composant
- Le noyau d’une machine est choisi pour vous. Comme la fenêtre Ajouter, l’ordre
add machineprend un noyau qui convient au système de la machine : une machine ajoutée ainsi démarre sans autre réglage. - La distribution et le noyau demandés doivent être installés sur l’hôte. Une distribution (le
système de fichiers de la machine virtuelle) qui n’y est pas installée, demandée par l’option
--distrib=deaddou parset m1 distrib, est refusée, et le refus donne la liste des distributions installées ; il en va de même pour un noyau que cette distribution ne prévoit pas. Marionnet ne remplace donc jamais en silence ce que vous avez demandé. - Le nombre de ports suit les limites de l’interface : de 1 à 8 cartes réseau pour une machine, de 4 à 16 ports pour un hub, un switch, un routeur ou un Gateway, de 1 à 16 pour un NAT bridge ou un LAN bridge.
- Un ajout refusé ne laisse rien derrière lui : le nom reste libre, et le script peut réessayer sous le même nom, une fois l’erreur corrigée.
- Un câble ne se renomme pas, ni à la souris ni par un ordre : pour changer son nom, supprimez-le, puis reliez-en un nouveau.
Pourquoi il faut arrêter les machines avant d'enregistrer
Le fichier .mar contient, outre la description du réseau, le disque de chaque machine (ou plus
exactement ce qui a changé par rapport à l’image d’origine). Enregistrer une machine en marche
reviendrait donc à recopier un disque en pleine écriture, et rouvrir ensuite le projet
reviendrait à rallumer une machine après une coupure de courant. D’où le refus, qui est aussi
celui de l’interface graphique.
close --save règle la question, puisqu’il arrête les machines, attend qu’elles soient arrêtées,
puis enregistre. Cela peut prendre plusieurs dizaines de secondes, que l’option --timeout ne
borne pas : laissez donc au script le temps de l’arrêt.
Pour la même raison, un fichier .mar ne se fabrique pas à la main. La description du réseau y
est certes écrite en texte (cf. ../project-format-v3.md), mais les disques ne peuvent être
produits que par Marionnet : construisez donc le réseau par des ordres, et enregistrez-le par
save-as.
Adresses et anomalies, case par case
Ces deux onglets se lisent et s’écrivent par deux paires d’ordres :
- l’onglet Interfaces, par
mrnctl ifconfigetmrnctl ifconfig-set; - l’onglet Anomalies, par
mrnctl defectsetmrnctl defects-set.
L’écriture se fait une case à la fois, en désignant la colonne par un nom que donne le champ
slugs de la lecture :
mrnctl -q '.slugs[]' ifconfig m1
# mac-address
# mtu
# ipv4-address
# …
mrnctl ifconfig-set m1 eth0 ipv4-address 10.0.0.1/24
mrnctl defects-set m1 eth0 inward loss 5 --no-restart
mrnctl defects-set c1 rightward maximum-delay-ms 50
La valeur est contrôlée exactement comme dans l’interface graphique. Quand une modification exige
de redémarrer une machine en marche, l’interface vous demanderait s’il faut le faire tout de suite ;
un script doit répondre d’avance, par --restart ou --no-restart. Un câble branché reçoit en
revanche sa nouvelle anomalie à chaud, sans redémarrage.
6. Faire travailler les machines
Les ordres de Marionnet agissent sur le réseau, vu de l’extérieur. Pour ce qui se passe à l’intérieur d’une machine, il existe deux moyens.
-
La configuration de démarrage, la fonctionnalité que l’interface graphique propose sous ce nom, est un petit script bash que la machine exécute à la fin de son démarrage. Il peut configurer la machine, jouer sa part du TP (lancer un
ping, par exemple), écrire des résultats, puis signaler que la machine est prête :mrnctl rc-set m1 --from=/tmp/scenario.sh --enable mrnctl start m1 mrnctl wait m1 --ready --timeout=300Ce script étant lu au démarrage, il ne prend effet qu’au prochain démarrage de la machine.
-
L’ordre
execfait exécuter une commande à une machine déjà en marche, et rend sa sortie :mrnctl exec m1 uname -r mrnctl -q .output exec m1 -- ping -c 1 -W 2 10.0.0.2Le
--isolé sépare les options du pilote de celles de la commande : sans lui, une option comme--allserait prise par le pilote pour l’une des siennes.
Pour récupérer ce qu’une machine a produit, le plus simple est qu’elle l’écrive dans /mnt/hostfs.
Ce répertoire de la machine virtuelle est en réalité un répertoire de l’hôte, que votre script lit
directement ; mrnctl rc-get m1 en donne le chemin, dans le champ hostfs.
Un exemple de configuration de démarrage
# Le script que m1 exécutera à la fin de son démarrage,
# écrit ici sur l'hôte :
cat > /tmp/scenario.sh <<'EOF'
ip addr add 10.0.0.1/24 dev eth0 2>/dev/null
ip link set eth0 up
ping -c 3 10.0.0.2 > /mnt/hostfs/ping.log 2>&1
echo "$?" > /mnt/hostfs/ping.status
# Dernière ligne : signaler que la machine est prête.
echo ready > /mnt/hostfs/marionnet-guest-ready
EOF
# Le confier à m1, la démarrer, attendre qu'elle soit prête :
mrnctl rc-set m1 --from=/tmp/scenario.sh --enable
mrnctl start m1
mrnctl wait m1 --ready --timeout=300
# Lire, depuis l'hôte, ce que la machine a écrit :
hostfs=$(mrnctl -q '.hostfs' rc-get m1)
cat "$hostfs/ping.log"
Pour un script d’une seule ligne, le contenu peut aussi être donné directement dans l’ordre :
mrnctl rc-set m1 'echo bonjour'
Aujourd’hui, les machines, les switchs et les routeurs ont une configuration de démarrage ; les hubs, les nuages et les composants d’accès au réseau réel n’en ont pas.
Les démons de routage d'un routeur
Un routeur a, outre sa configuration Unix, une configuration par démon de routage, de même que sa
fenêtre Modifier a un onglet par protocole. Demandez-lui lesquels il possède, puis nommez-en un
par --field= :
mrnctl -q '.available | join(" ")' rc-get r1
# rc_config_unix zebra rip ripng ospf bgp ospf6 isis
mrnctl rc-set r1 --from=/tmp/zebra.conf --field=zebra
# Ce démon-ci ne démarrera pas :
mrnctl rc-set r1 --field=ospf --unselect
# Ouvrir le terminal de zebra, à la CISCO-IOS :
mrnctl rc-set r1 --field=zebra --terminal
Écrire la configuration d’un démon coche à la fois sa case activée (votre texte est utilisé) et
sa case sélectionnée (le démon démarre), car une configuration écrite pour rien serait une
mauvaise surprise ; --unselect et les options voisines permettent d’en décider autrement.
Les guillemets dans exec
Supposons que vous vouliez faire exécuter à m1 la commande sh -c 'exit 7', qui doit rendre le
code 7. Avant d’atteindre la machine, cette commande passe par votre shell, qui retire ses
propres guillemets : il faut donc la transmettre en un seul argument, entre guillemets doubles.
# CORRECT : la machine reçoit sh -c 'exit 7' (code 7).
mrnctl exec m1 "sh -c 'exit 7'"
# ERREUR : votre shell a retiré les guillemets,
# la machine reçoit sh -c exit 7 (code 0).
mrnctl exec m1 -- sh -c 'exit 7'
Ce que Marionnet garde de chaque exec, et ses limites
Chaque commande exécutée par exec est notée, avec sa date et son code de retour, dans un journal
propre à la machine, le journal exec (cf. § 7) : à la correction, elle ne se confond donc jamais
avec ce que l’étudiant a tapé lui-même.
Deux limites protègent par ailleurs le script : la sortie rendue est limitée à 200 lignes, et
l’option --timeout=<s> borne la durée de la commande. À l’expiration du délai, la commande est
arrêtée dans la machine, et la réponse le signale.
7. Savoir ce qui s’est passé
Marionnet sait ce qu’il a construit, et les machines savent ce qui s’est passé en elles ; les uns comme les autres s’interrogent :
# Les composants et leur état, puis les adresses de m1 :
mrnctl ls
mrnctl ifconfig m1
# Ce qu'a fait la configuration de démarrage de m1 :
mrnctl log m1
# Ce qui a été tapé dans son terminal :
mrnctl log m1 commands
# Lui demander un rapport sur son état, puis le lire :
mrnctl report m1
mrnctl log m1 report
Le rapport mérite une explication. Les journaux sont des traces : ils disent quelles commandes ont été lancées, mais pas toujours l’état qui en résulte. Supposons par exemple que la configuration de démarrage d’une machine contienne la ligne suivante :
echo 1 > /proc/sys/net/ipv4/ip_forward
Le transfert de paquets est alors bien activé, mais le journal de la configuration de démarrage
n’en garde que echo 1, car sa trace ne note pas les redirections. Le rapport, lui, est une
photographie de la machine à un instant donné (ses adresses réelles, ses routes, ses voisins, son
pare-feu) : c’est donc sur lui qu’il faut s’appuyer pour savoir si une machine est dans l’état
voulu.
Enfin, l’onglet Historique se pilote lui aussi : mrnctl history m1 liste les états de disque
d’une machine, et history-start redémarre la machine depuis l’un d’eux, comme l’entrée Démarrer
dans cet état de l’interface, ce qui permet de rejouer un TP à partir d’un point connu.
Les sept journaux d'une machine
| Journal | Écrit par | Contenu |
|---|---|---|
rc_config |
la machine | la trace de sa configuration de démarrage, ses erreurs, et la commande qui a échoué (un switch a aussi ce journal, écrit par Marionnet) |
boot |
la machine | ce qu’a fait son démarrage : messages du noyau, services en échec |
commands |
la machine | les commandes tapées, chacune avec sa date |
report |
la machine, sur demande ou à son arrêt propre | le rapport décrit ci-dessus |
exec |
la machine | les commandes exécutées par mrnctl exec, avec leur date et leur code de retour |
console |
Marionnet, sur l’hôte | la console de la machine, depuis la première ligne (option --console-log) |
terminal |
Marionnet, sur l’hôte | l’enregistrement du terminal, commandes et sorties (option --terminal-log) |
Les deux derniers n’existent que si Marionnet a été lancé avec l’option correspondante, car
enregistrer une session à l’insu de l’utilisateur relèverait de la surveillance plutôt que de
l’enseignement ; --exam active les deux (cf. ../exam-mode.FR.md). Notons aussi que les cinq
premiers sont écrits dans un répertoire où l’étudiant peut écrire, alors que la console et le
terminal, écrits par l’hôte, sont hors de sa portée.
Un journal survit à ce qu’il décrit : une machine éteinte répond toujours à log. Quand un journal
n’existe pas encore, le refus explique pourquoi (machine jamais démarrée, démarrage pas encore
terminé, option absente au lancement). Une réponse porte au plus 400 lignes, dont --tail=<n>
permet de choisir les dernières.
Ce que sait un switch en ce moment
mrnctl switch-info s1 demande à un switch en marche ce qu’il sait à l’instant même : ses ports, sa
table d’adresses MAC, ses VLAN, son arbre couvrant. Ces informations ne sont écrites nulle part, et
disparaissent avec le switch ; ce qu’il a dit à son démarrage reste en revanche dans son journal
(mrnctl log s1).
8. Écrire les ordres dans un fichier
Plutôt qu’un script shell, un TP peut s’écrire comme une simple liste d’ordres, un par ligne, dans
un fichier .mrn :
cat > tp.mrn <<'EOF'
new /tmp/tp.mar
add machine m1
add machine m2
add switch s1 --ports=4
connect c1 m1:eth0 s1:port1
connect c2 m2:eth0 s1:port2
save-as /tmp/tp.mar
EOF
mrnctl -f tp.mrn
Le fichier s’exécute ligne à ligne, et s’arrête au premier refus (--keep-going permet de
continuer). Notons qu’un échec au milieu du fichier laisse le projet dans l’état où l’ont mis les
lignes précédentes, puisque rien n’est annulé ; d’où l’intérêt de vérifier le fichier avant de
l’envoyer, ce que fait mrn-check (installé, comme les deux outils qui suivent, avec Marionnet),
sans rien envoyer à Marionnet :
mrn-check tp.mrn
# tp.mrn: 9 request(s), no error
Il signale les ordres inconnus, les ports qui ne peuvent pas exister (port0 sur un switch), les
composants utilisés avant d’avoir été ajoutés, ou encore les ports déjà occupés.
Enfin, quand un TP a besoin de plus qu’une liste d’ordres (attendre une machine, lire un résultat,
répéter une opération sur chaque machine), mrn2sh traduit le fichier en un script shell
ordinaire, que vous complétez ensuite à la main :
mrn2sh tp.mrn > tp.sh
Vérifier sans Marionnet en marche, et détails de la traduction
mrn-check demande le vocabulaire au Marionnet en cours d’exécution. Pour vérifier un fichier sans
Marionnet, prenez une fois un instantané du vocabulaire, que vous reprendrez quand Marionnet
changera :
mrnctl help > grammar.json
mrn-check --grammar=grammar.json tp.mrn
Les vérifications portant sur les noms supposent que le fichier construise lui-même le projet,
c’est-à-dire qu’il commence par new. Après un open, mrn-check ne connaît pas le projet : il
ne vérifie alors que la syntaxe et les noms de ports, et le dit. Ce qui ne peut se décider qu’en
marche (une machine peut-elle démarrer maintenant ?) reste l’affaire de Marionnet.
mrn2sh refuse de traduire un fichier qui contient une erreur. Le script qu’il produit affiche
chaque ordre avant de l’envoyer, conserve vos commentaires, et accepte en argument un autre
fichier de projet que celui du .mrn :
./tp.sh /tmp/autre.mar
La traduction est à sens unique : une fois le script modifié, c’est lui qui devient la source, et le régénérer l’écraserait.
9. Vérifier un TP
Un fichier .mrn dit de quoi un réseau est fait ; un fichier .mrv dit ce qui doit en être
vrai, à raison d’une affirmation par ligne, et mrn-verify le confronte à la session en cours :
cat > tp.mrv <<'EOF'
state m1 is on
cable c1 m1:eth0 s1:port1
journal m1 rc_config ok
report m1 says ~ net[.]ipv4[.]ip_forward *= *1
reaches m1 m2
EOF
mrn-verify tp.mrv
# PASS state m1 is on
# PASS cable c1 m1:eth0 s1:port1
# …
Chaque ligne reçoit l’un de trois verdicts : PASS si l’affirmation est vraie, FAIL si elle est
fausse, et SKIP quand Marionnet n’offre aucun moyen de le savoir, par exemple pour un ping vers
une machine éteinte, dont l’adresse ne peut pas être lue. Notons que reaches m1 m2 lance un ping
depuis m1 vers l’adresse de m2, lue dans le rapport de m2 : un pare-feu qui bloque ICMP donne
donc FAIL, comme le ferait un ping tapé à la main. Ce troisième verdict a son importance, car un
vérificateur qui répondrait FAIL dans ce cas sanctionnerait un étudiant pour une limite de
l’outil ; l’option --strict permet toutefois de compter un SKIP comme un échec. La liste des
affirmations possibles (neuf formes) est donnée par mrn-verify --help, et la vérification ne
modifie rien dans le projet. Enfin, un motif précédé de ~, comme dans la ligne report de
l’exemple, est une expression régulière étendue (celle de grep -E) ; sans ~, le motif est un
texte cherché tel quel.
Les neuf formes d'affirmation (Marionnet 1.0.470)
Telles que les donne mrn-verify --help :
state <component> is on|off|sleeping
field <component> <field> is <value>
cable <cable> <node>:<port> <node>:<port>
switch <switch> <table> has <key>=<value> [<key>=<value>]…
journal <component> <journal> contains|lacks <pattern>
journal <component> <journal> ok
documents <component> has <pattern>
report <component> says|lacks <pattern>
reaches <component> <component>|<address>
La trace et l'état : deux affirmations qui se ressemblent
journal m1 rc_config contains ip_forward
report m1 says ~ net[.]ipv4[.]ip_forward *= *1
La première lit la trace de la configuration de démarrage, la seconde le rapport. Sur la machine
de l’exemple du § 7, dont la configuration active le transfert de paquets par une redirection, la
première échoue, puisque la trace ne contient que echo 1, alors que la seconde réussit. Un
corrigé doit donc s’appuyer sur la trace pour prouver qu’une commande a été lancée, et sur le
rapport pour prouver son effet, c’est-à-dire l’état dans lequel la machine se trouve
réellement.
Pour un script ou une IA : codes de retour et sortie JSON
mrn-verify rend 0 si tout est vrai, 1 si une affirmation est fausse (ou si le fichier contient
une erreur), et 2 si rien n’a pu être vérifié. --json donne un objet par affirmation, avec sa
ligne et sa raison. Comme mrn-check, il peut vérifier le fichier lui-même hors session
(--grammar=grammar.json), et dit alors d’avance ce qui ne pourra pas être vérifié. Enfin,
--refresh=never lit le dernier rapport de chaque machine au lieu d’en demander un nouveau, ce qui
convient pour noter une session terminée.
10. Quand ça ne marche pas
| Ce que vous voyez | Ce qu’il faut faire |
|---|---|
Marionnet refuse de démarrer (--control-socket: …) |
le chemin est relatif, trop long, ou déjà utilisé par un autre Marionnet : le message précise lequel |
no control socket |
indiquez le point de rendez-vous (export MARIONNET_CONTROL_SOCKET=…) |
not a unix socket |
le chemin est faux, ou bien Marionnet a été lancé sans --control-socket |
no answer from … |
Marionnet a disparu, ou une fenêtre attend une réponse à l’écran (avec --keep-dialogs, c’est à l’humain de la fermer) |
no_active_project |
créez ou ouvrez d’abord un projet |
unsaved_changes |
précisez --save ou --no-save |
components_running à l’enregistrement |
arrêtez d’abord les machines, ou utilisez close --save |
forbidden_transition |
l’interface graphique l’interdirait aussi : demandez mrnctl can <composant> |
restart_choice_required |
ajoutez --restart ou --no-restart |
timeout sur wait … --ready |
la machine n’a pas écrit le fichier convenu : vérifiez que sa configuration de démarrage est activée (--enable) et qu’elle a bien démarré |
| réponse tardive à un ordre long | augmentez son --timeout= ; le pilote règle sa propre attente sur cette valeur |
11. Pour aller plus loin
mrnctl helpdonne tous les ordres, toujours à jour.mrnctl --help,mrn-check --helpetmrn-verify --helpdonnent les options de chaque outil.examples/contient des scripts complets, prêts à être exécutés.../teacher-guide.FR.mdprésente le pilotage vu depuis une salle de TP (préparer un TP, mener la séance, corriger), avec un TP complet dans../labs/session-7/.../exam-mode.FR.mddécrit le mode examen.../marionnet-driving-skill.mdest le guide à donner à une IA pour qu’elle pilote Marionnet, c’est-à-dire pour qu’elle construise, vérifie et note un TP par des commandes.
Pour les développeurs
Dans les sources de Marionnet :
docs/pilotage-par-script.md: la conception de l’interface de pilotage, ses motivations et le journal de sa construction ;docs/journalisation-profonde.md: la conception des journaux ;bin/scripts/marionnet-ctl.sh: le pilote.
Ce guide décrit la forme de l’interface et ses règles durables. Il ne recopie ni la liste des
ordres ni leurs options, que le Marionnet en cours d’exécution publie lui-même par help : en cas
de désaccord, c’est donc help qui a raison.
