Exploitation IS-IS en pratique

Manuel pratique IS-IS Watcher

Faites un seul changement controle a la fois, predisez son effet, puis verifiez le meme fait depuis l'evenement du Watcher jusqu'a Topolograph.

Demarrer le manuel

Liens utiles : IS-IS Watcher - depot et guide de deploiement · Topolograph

1. Comment fonctionne ce manuel

Pour chaque exercice : faites un changement, predisez le resultat IS-IS, observez l'evenement du Watcher, puis retrouvez le meme fait dans Topolograph Monitoring, dans le SDK et - pour l'exercice de correlation - dans la reponse de l'agent. Restaurez le laboratoire avant l'exercice independant suivant.

isis01 est un domaine FRR de six routeurs dans une seule aire (49.0001, AS 65100). router2-router3 fonctionne en Level-1-2 ; router3 vers router6 et la patte de router3 vers le LAN router4/router5 sont Level-2 uniquement. Le Watcher utilise une adjacence Level-2 sur router1 et resout chaque System ID en son hostname via le TLV de hostname dynamique d'IS-IS, si bien que les evenements nomment router2, router3, router6 plutot que 0100.1001.000x.

  1. La ligne CSV du Watcher est l'evenement source deterministe.
  2. Monitoring et le SDK prouvent que l'evenement a ete ingere et est interrogeable.
  3. L'agent sert uniquement a correler plusieurs evenements en un incident, jamais a remplacer la ligne source.

2. Mise en route et validation du laboratoire à six routeurs

Lancez la topologie publique isis01 basee sur GRE du depot IS-IS Watcher. prepare.sh cree aussi le pont isis-br-dr et charge les modules noyau MPLS dont IS-IS TE a besoin.

Verifiez d'abord si un laboratoire tourne deja avec sudo clab inspect --all. Si un isis01 obsolete est liste, demontez-le avec sudo clab destroy --topo isis01.clab.yml --cleanup depuis containerlab/isis01/ avant de redeployer ; nommez le fichier de topologie pour ne toucher aucun autre laboratoire.

Commande

cd containerlab/isis01
sudo clab inspect --all
sudo clab destroy --topo isis01.clab.yml --cleanup   # only if a stale isis01 is listed
sudo ./prepare.sh
sudo clab deploy --topo isis01.clab.yml
sudo docker logs clab-isis01-isis-watcher
sudo tail -f watcher/logs/watcher1.isis.log
Observations requises Faits a confirmer avant de continuer
  • Six conteneurs de routeur plus le watcher sont actifs : docker ps --filter name=clab-isis01 liste clab-isis01-router1..6 et clab-isis01-isis-watcher, tous en State=Up.
  • Le watcher possede la LSDB et capture le trafic : docker logs clab-isis01-isis-watcher affiche ISIS LSDB has been received et Sniffing packets on interface: eth1.
  • Les adjacences sont Up : docker exec clab-isis01-router1 vtysh -c 'show isis neighbor' liste router3 a l'etat Up ; le premier graphe Topolograph contient alors six routeurs.

Retour arriere : Quand vous avez fini le manuel, supprimez le laboratoire avec sudo clab destroy --topo isis01.clab.yml --cleanup depuis containerlab/isis01/.

3. Format de l'enregistrement d'événement réseau

Le manuel utilise trois familles d'evenements : host, metric et network. Lisez event_object comme l'objet modifie, event_status comme la transition et event_detected_by comme le routeur qui annonce ou detecte. graph_time est l'etiquette propre du watcher pour l'execution et selectionne le graphe Topolograph.

Les lignes IS-IS portent un champ level (1 ou 2) que les lignes OSPF n'ont pas : c'est le troisieme champ, juste apres watcher_name. Lisez la ligne metric ci-dessus comme une phrase : a 2026-09-07T06:55:14Z, le watcher lab-isis01 a vu router3 reannoncer son lien Level-1 vers router2 avec la metrique passee de 10 a -1 (adjacence perdue), sur l'interface adressee 192.168.23.2 dans l'aire 49.0001 / AS 65100. L'identite derriere router2 est son NET / System ID 49.0001.0100.1001.0002.00 ; le watcher affiche le hostname car chaque routeur annonce le TLV de hostname dynamique.

Une simple ligne host ou network up/down ne porte pas de champs de cout, donc Fluent Bit ne transmet que la ligne changed associee. Topolograph deduit qu'une adjacence tombe a partir de la ligne metric dont le new_cost vaut -1, et qu'elle revient a partir de celle dont le old_cost vaut -1.

  1. Champs dans l'ordre : watcher_time, watcher_name, level, event_name, event_object, event_status, [champs de cout], event_detected_by, graph_time, area_num, asn, [local_ip, remote_ip | subnet_type, int_ext_subtype], sesid, srcid.
  2. area_num 49.0001 et asn 65100 identifient le domaine de routage ; sesid est la session du watcher, srcid le System ID du routeur source (router1, 0100.1001.0001).

CSV de Watcher

host:    2026-09-07T06:55:14.754Z,lab-isis01,1,host,router2,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
metric:  2026-09-07T06:55:14.756Z,lab-isis01,1,metric,router2,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
network: 2026-09-07T06:55:14.761Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001

4. D'où viennent les événements

Un changement IS-IS produit une ligne CSV du Watcher par niveau. Fluent Bit la transmet a Topolograph, qui la stocke et l'expose via la page Monitoring, l'API d'evenements et le SDK. Chaque exercice ci-dessous verifie le meme fait a chaque niveau disponible.

  1. Journal du Watcher isis01 -> parseur CSV Fluent Bit -> ingestion Topolograph -> page Monitoring + API d'evenements -> SDK Topolograph
  2. Page Monitoring : OSPF/IS-IS Real-Time Monitoring. Choisissez le graphe par son horodatage dans Choose the graph, definissez la fenetre From/To en UTC, activez les bascules L1 et L2, cliquez sur Find logs ; les bascules New/Old Subnets, Up/Down Links et Changed metric filtrent ce qui est liste.
  3. Le sélecteur de graphe liste chaque instantané de topologie rapporté par le watcher : c'est la topologie que le watcher a envoyée.
Controles de Topolograph OSPF/IS-IS Real-Time Monitoring pour le graphe 07Sep2026_06h49m43s_6_hosts : le selecteur de graphe, la fenetre de temps From/To, les bascules de niveau L1 et L2 toutes deux activees, les bascules New/Old Subnets, Up/Down Links et Changed metric, et Find logs. Le panneau Watchers Status affiche "No watchers registered yet" car le watcher containerlab envoie la topologie mais pas de heartbeats.

Requete du SDK

from topolograph import Topolograph

topo = Topolograph(url="http://<your-topolograph>:8080",
                   username="<email>", password="<password>")
graph = topo.graphs.get(latest=True)
print(graph.graph_time, graph.protocol, graph.hosts)

Sortie du SDK

07Sep2026_06h49m43s_6_hosts isis {'count': 6}

5. Changement de coût sur un lien point-to-point

Changez router2 eth1 vers router3. Les metriques IS-IS sont dirigees, donc la metrique inverse de router3 vers router2 ne doit pas etre decrite comme le meme scalaire. router2 eth1 n'a pas de isis metric explicite, il part donc de la valeur wide-metric par defaut de 10. router2-router3 est un circuit Level-1-2, le changement est donc annonce aux deux niveaux.

Commande

sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'isis metric 222'

Verifiez le changement. Ouvrez chaque source disponible dans votre environnement et comparez-la aux exemples ci-dessous.

Journal du Watcher Les lignes metric et network du changement et de son retour arriere, en L1 et L2 L1 + L2

CSV de Watcher

# router2: interface eth1 / isis metric 222
2026-09-07T06:52:15.035Z,lab-isis01,1,metric,router3,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:15.037Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:15.045Z,lab-isis01,2,metric,router3,changed,old_cost:10,new_cost:222,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# rollback (isis metric 10):
2026-09-07T06:52:37.514Z,lab-isis01,1,metric,router3,changed,old_cost:222,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:37.526Z,lab-isis01,2,metric,router3,changed,old_cost:222,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
Supervision Le changement de metrique 10 -> 222 dans le flux d'evenements

Dans OSPF/IS-IS Real-Time Monitoring, choisissez 07Sep2026_06h49m43s_6_hosts dans Choose the graph, definissez la fenetre From/To autour de 06:52 UTC, activez L1 et L2 et cliquez sur Find logs. Avec Changed metric active, le flux affiche object router3, detected by router2, 10 -> 222 - uniquement la direction router2 -> router3, une fois pour L1 et une fois pour L2. La direction inverse n'apparait pas.

Verification par SDK get_adjacency_events et get_network_events renvoient le meme objet et les memes couts 2 evenements

Requete du SDK

adj = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:52:14Z", end_time="2026-09-07T06:52:20Z")
net = graph.events.get_network_events(
    start_time="2026-09-07T06:52:14Z", end_time="2026-09-07T06:52:20Z")
for e in adj["adjacency_cost_change_events"]:
    print(e.event_object, e.event_detected_by, e.old_cost, "->", e.new_cost, "L" + str(e.level_number))
for e in net["network_cost_change_events"]:
    print(e.event_object, e.old_cost, "->", e.new_cost, "L" + str(e.level_number))

Sortie du SDK

router3 router2 10 -> 222 L1
router3 router2 10 -> 222 L2
3ffe::192:168:23:2/127 10 -> 222 L1
192.168.23.0/24 10 -> 222 L1
3ffe::192:168:23:2/127 10 -> 222 L2
192.168.23.0/24 10 -> 222 L2

L'evenement metric nomme la direction router2 -> router3 (event_object router3, event_detected_by router2), 10 -> 222, aux deux niveaux ; les sous-reseaux connectes IPv4 et IPv6 portent le meme changement. La direction inverse est absente.

Demander a l'agent Une question d'incident generique sur router2

Prompt : What happened with router2 in the last 10 minutes?

Une reponse valable nomme la direction router2 -> router3 et les deux valeurs de metrique (10 et 222) sans que la question mentionne cout ou metrique, et n'affirme pas que la direction inverse router3 -> router2 a change.

Retour arriere : Executez isis metric 10 (ou no isis metric) sur router2 eth1 et confirmez les evenements inverses 222 -> 10 metric et network.

6. Détection des événements de préfixes internes et externes

Executez chaque experience de prefixe separement. Contrairement a OSPF, IS-IS annonce un loopback avec le masque configure et lui applique la metrique de l'interface : un /24 reste un /24 au cout 10, il n'est pas reduit a une route hote /32. router6 est Level-2 uniquement, donc ses prefixes n'apparaissent qu'en L2.

  1. 6a. Sur router2, interface lo / ip address 192.168.123.1/24 ; observez 192.168.123.0/24 up et cout -1 -> 10 en L1 et L2.
  2. 6b. Sur router6, interface lo / ip address 10.10.36.6/24 ; observez 10.10.36.0/24 up et cout -1 -> 10 en L2.
  3. 6c. Sur router6, no ip route 6.6.6.6/32 192.168.36.3 ; observez 6.6.6.6/32 down et cout 11 -> -1 en L2. FRR la redistribue dans IS-IS sans le bit external, donc le watcher l'etiquette internal.

Commande

sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface lo' -c 'ip address 192.168.123.1/24'

Verifiez le changement. Ouvrez chaque source disponible dans votre environnement et comparez-la aux exemples ci-dessous.

Journal du Watcher Une ligne up/down et une ligne changed par sous-exercice 3 prefixes

CSV de Watcher

# 6a router2: interface lo / ip address 192.168.123.1/24
2026-09-07T06:52:52.074Z,lab-isis01,1,network,192.168.123.0/24,up,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:52:52.075Z,lab-isis01,1,network,192.168.123.0/24,changed,old_cost:-1,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# 6b router6: interface lo / ip address 10.10.36.6/24
2026-09-07T06:53:39.099Z,lab-isis01,2,network,10.10.36.0/24,up,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:53:39.099Z,lab-isis01,2,network,10.10.36.0/24,changed,old_cost:-1,new_cost:10,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# 6c router6: no ip route 6.6.6.6/32 192.168.36.3
2026-09-07T06:54:14.533Z,lab-isis01,2,network,6.6.6.6/32,down,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:54:14.533Z,lab-isis01,2,network,6.6.6.6/32,changed,old_cost:11,new_cost:-1,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
Supervision Les prefixes nouveaux et retires dans le flux

Dans OSPF/IS-IS Real-Time Monitoring, choisissez ce graphe, definissez la fenetre autour de 06:52-06:55 UTC, activez L1 et L2 et cliquez sur Find logs. Avec New/Old Subnets active, 192.168.123.0/24 et 10.10.36.0/24 apparaissent comme ajoutes et 6.6.6.6/32 comme retire.

Verification par SDK get_network_events, un evenement changed par sous-exercice 3 evenements

Requete du SDK

for start, end in [("2026-09-07T06:52:50Z", "2026-09-07T06:52:55Z"),
                   ("2026-09-07T06:53:37Z", "2026-09-07T06:53:42Z"),
                   ("2026-09-07T06:54:12Z", "2026-09-07T06:54:17Z")]:
    net = graph.events.get_network_events(start_time=start, end_time=end)
    for e in net["network_up_down_events"]:
        print(e.event_object, e.event_status, e.event_detected_by,
              f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number), e.subnet_type)

Sortie du SDK

192.168.123.0/24 changed router2 -1 -> 10 L1 internal
192.168.123.0/24 changed router2 -1 -> 10 L2 internal
10.10.36.0/24 changed router6 -1 -> 10 L2 internal
6.6.6.6/32 changed router6 11 -> -1 L2 internal

Le /24 conserve son masque : pas de reduction a /32. La disponibilite est une ligne up/down que le forwarder abandonne ; le changement de cout est l'evenement changed que le SDK renvoie. Le loopback de router2 est vu en L1 et L2, celui de router6 seulement en L2, et le 6.6.6.6/32 redistribue est etiquete internal.

Retour arriere : 6a : no ip address 192.168.123.1/24 sur router2 interface lo. 6b : no ip address 10.10.36.6/24 sur router6 interface lo. 6c : ip route 6.6.6.6/32 192.168.36.3 sur router6. Confirmez l'evenement inverse apres chacun.

7. Détection d'une perte de connectivité et de son rétablissement

Desactivez router2 eth1, inspectez l'ensemble d'evenements correles, puis restaurez l'interface avec no shutdown. Comme router2-router3 est Level-1-2, chaque ligne apparait une fois pour L1 et une fois pour L2.

Commande

# down
sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'shutdown'
# recovery
sudo docker exec clab-isis01-router2 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'no shutdown'

Verifiez le changement. Ouvrez chaque source disponible dans votre environnement et comparez-la aux exemples ci-dessous.

Journal du Watcher L'ensemble down de L1 et l'ensemble de retablissement de L1 pour le lien router2-router3 down + up

CSV de Watcher

# router2: interface eth1 / shutdown
2026-09-07T06:55:14.754Z,lab-isis01,1,host,router2,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.756Z,lab-isis01,1,metric,router2,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.760Z,lab-isis01,1,metric,router3,changed,old_cost:10,new_cost:-1,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:14.761Z,lab-isis01,1,network,192.168.23.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router2: interface eth1 / no shutdown
2026-09-07T06:55:39.545Z,lab-isis01,1,metric,router3,changed,old_cost:-1,new_cost:10,router2,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:55:39.607Z,lab-isis01,1,metric,router2,changed,old_cost:-1,new_cost:10,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.23.2,192.168.23.1,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
Supervision La panne comme une seule vague dans le flux

Dans OSPF/IS-IS Real-Time Monitoring, choisissez ce graphe, definissez la fenetre autour de 06:55 UTC, activez L1 et L2 et cliquez sur Find logs. Avec Up/Down Links active, router2 et router3 montrent chacun leur cote du lien passant a -1 puis revenant, et 192.168.23.0/24 devient injoignable puis revient - deux fois, une par niveau.

Verification par SDK get_adjacency_events renvoie les mouvements down/up apparies 4 mouvements

Requete du SDK

down = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:55:14Z", end_time="2026-09-07T06:55:16Z")
for e in down["all_host_up_down_events"]:
    print(e.event_object, "detected_by", e.event_detected_by,
          f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))

Sortie du SDK

router2 detected_by router3 10 -> -1 L1
router3 detected_by router2 10 -> -1 L1
router2 detected_by router3 10 -> -1 L2
router3 detected_by router2 10 -> -1 L2

host router2 down detecte par router3, les deux metriques dirigees a -1, 192.168.23.0/24 down - en L1 et L2 ; puis le retablissement en miroir. Au retablissement, le watcher enregistre aussi un flap node attr:attached sur router2, que Topolograph n'ingere pas encore.

Demander a l'agent Une question d'incident generique sur le reseau

Prompt : What happened in the network in the last 30 minutes?

Reponse capturee (Qwen ; la formulation varie d'une execution a l'autre)

In the last 30 minutes the IS-IS domain (area 49.0001, AS 65100) had 7 event waves detected by watcher lab-isis01, starting at 06:55:15 UTC. Every device that went down recovered within the window.

06:55:15 - router2 and router3 each lost the adjacency between them, at L1 and L2 (router2 detected by router3, router3 detected by router2).

06:55:40 - both routers restored that adjacency at L1 and L2, followed by a few repeated up notifications through 06:56:33.

06:57:23 - router6 lost its L2 adjacency to router3; 06:57:56 - router6 and router3 restored it.

In short: the router2-router3 link dropped at 06:55:15 and came back at 06:55:40; the router3-router6 link dropped at 06:57:23 and recovered at 06:57:56 - both fully converged.

La reponse nomme l'adjacence tombee (router2 - router3), le fait que router3 a detecte la perte de router2 et inversement, les deux metriques a -1 et le retablissement - les memes faits que les lignes CSV ci-dessus, a partir d'une question qui ne dit jamais "adjacency" ni "failure". Les lignes router6 correspondent a l'exercice de transit ci-dessous, capture par la meme fenetre de 30 minutes.

Retour arriere : no shutdown sur router2 eth1 restaure l'interface ; attendez les evenements de retablissement host, network et metric aux deux niveaux.

8. Compte rendu d'un segment de transit broadcast

router6 eth1 fait face a router3 sur un circuit broadcast (LAN). router6 porte isis priority 100 contre 64 pour router3, donc router6 est le DIS et origine le LSP de pseudonoeud. Le circuit est Level-2 uniquement, donc chaque ligne est L2. router6 eth1 n'a pas de isis metric explicite, donc la base est la valeur par defaut 10.

Commande

# cost
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'isis metric 66'
# then, separately: shutdown, then no shutdown
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'shutdown'
sudo docker exec clab-isis01-router6 vtysh \
  -c 'conf t' -c 'interface eth1' -c 'no shutdown'

Verifiez le changement. Ouvrez chaque source disponible dans votre environnement et comparez-la aux exemples ci-dessous.

Journal du Watcher Le changement de cout L2 avec ses effets de bord sur network, plus la paire shutdown/retablissement cout + up/down

CSV de Watcher

# router6: interface eth1 / isis metric 66   (rollback: isis metric 10)
2026-09-07T06:56:47.139Z,lab-isis01,2,network,192.168.36.0/24,changed,old_cost:10,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:56:47.140Z,lab-isis01,2,metric,router3,changed,old_cost:10,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:04.561Z,lab-isis01,2,metric,router3,changed,old_cost:66,new_cost:10,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router6: interface eth1 / shutdown
2026-09-07T06:57:22.037Z,lab-isis01,2,network,192.168.36.0/24,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,internal,0,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:22.055Z,lab-isis01,2,host,router6,down,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:22.055Z,lab-isis01,2,metric,router6,changed,old_cost:10,new_cost:-1,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
# router6: interface eth1 / no shutdown
2026-09-07T06:57:55.962Z,lab-isis01,2,host,router6,up,router3,07Sep2026_06h49m43s_6_hosts,49.0001,65100,192.168.36.3,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
2026-09-07T06:57:55.973Z,lab-isis01,2,metric,router3,changed,old_cost:-1,new_cost:66,router6,07Sep2026_06h49m43s_6_hosts,49.0001,65100,,,e50b9d98-aa86-11f1-92b0-46ed252a638b,0100.1001.0001
Supervision Le changement de cout de transit et la panne de router6, L2 uniquement

Dans OSPF/IS-IS Real-Time Monitoring, choisissez ce graphe, definissez la fenetre autour de 06:56-06:58 UTC, activez L2 et cliquez sur Find logs. L'object de l'evenement metric est router3 avec event_detected_by router6, et le changement est L2 uniquement - le circuit est level-2-only. Au shutdown, le flux affiche host router6 down et 192.168.36.0/24 injoignable, puis le retablissement.

Verification par SDK get_adjacency_events pour le cout de transit et le shutdown/retablissement L2 uniquement

Requete du SDK

cost = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:56:46Z", end_time="2026-09-07T06:56:49Z")
for e in cost["adjacency_cost_change_events"]:
    print("cost", e.event_object, e.event_detected_by, f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))
flap = graph.events.get_adjacency_events(
    start_time="2026-09-07T06:57:21Z", end_time="2026-09-07T06:58:00Z")
for e in flap["all_host_up_down_events"]:
    print("updown", e.event_object, e.event_detected_by, f"{e.old_cost} -> {e.new_cost}", "L" + str(e.level_number))

Sortie du SDK

cost router3 router6 10 -> 66 L2
updown router6 router3 10 -> -1 L2
updown router6 router3 -1 -> 10 L2
updown router3 router6 -1 -> 10 L2

Sur le segment broadcast, l'evenement metric nomme toujours le voisin (router3), detecte par router6, et uniquement en L2 ; le changement de metrique deplace aussi 192.168.36.0/24 et les deux /127 IPv6. Le shutdown est host router6 down en L2, puis le retablissement.

Demander a l'agent Une question d'incident generique sur router6

Prompt : What happened on router6 in the last 20 minutes?

Une reponse valable nomme la metrique de transit router6 -> router3 passant de 10 a 66 puis revenant, puis le rattachement de router6 qui tombe (metrique -1 en L2) et se retablit, sans que la question mentionne cout, DIS ou shutdown.

Retour arriere : isis metric 10 (ou no isis metric) sur router6 eth1 restaure la base 10 ; no shutdown restaure l'adjacence. Confirmez les evenements metric inverses.

9. Charger le laboratoire IS-IS à 13 routeurs

Le laboratoire à six routeurs permet d'examiner les événements Watcher et les attributs TE, mais n'offre pas assez de chemins alternatifs pour des exercices CSPF utiles. Utilisez le laboratoire 13-hosts-demo-isis : déployez-le, ou évitez le déploiement et téléchargez la LSDB prête à l'emploi demo_isis_LSDB.txt.

Chargez la LSDB dans Topolograph comme fichier FRR IS-IS : celle capturée sur r30 ou demo_isis_LSDB.txt téléchargé. Le graphe obtenu comporte 13 nœuds et 52 arêtes dirigées : 12 au niveau 1 et 40 au niveau 2. Cinquante arêtes portent des attributs TE ; les deux directions de r110-r111 n'en portent pas.

Commande

cd containerlab/13-hosts-demo-isis
sudo clab deploy --topo 13-hosts-demo-isis.clab.yml
sudo docker exec clab-13-hosts-demo-isis-r30 vtysh -c 'show isis database detail'

Requete du SDK

from topolograph import Topolograph

topo = Topolograph(url="http://<your-topolograph>:8080", username="<email>", password="<password>")
graph = topo.graphs.upload(open("demo_isis_LSDB.txt").read(), vendor="FRR", protocol="isis")
print(graph.graph_time, graph.hosts)
print(graph.edges_list(per_page=200)["pagination"]["total"])
print(graph.edges_list(is_te_link=True, per_page=200)["pagination"]["total"])
print(graph.edges_list(is_te_link=False, per_page=200)["pagination"]["total"])

Sortie du SDK

<new graph time> {'count': 13}
52
50
2
Observations requises Faits a confirmer avant de continuer
  • Le champ hosts du graphe chargé contient {'count': 13} et le nombre total d'arêtes est 52.
  • edges_list(is_te_link=True) retourne 50 arêtes ; edges_list(is_te_link=False) retourne les deux directions de r110-r111.

10. Lire les valeurs TE avant le calcul

Examinez les attributs TE avant d'appliquer une contrainte. Repérez les groupes administratifs gold et red, les métriques TE élevées, le petit pool de bande passante de priorité 7, les liens portant des SRLG et les liens non-TE. Un calcul de plus court chemin simple ne dit pas si un lien convient à un tunnel.

Sur ce graphe, gold (0x00000002) correspond à 10 arêtes dirigées, red (0x00000004) à 2, temetric__gt=30 à 4, unreserved_bw_7__lt=1000000 à 2, et is_te_link=False retourne les deux directions de r110-r111.

Commande

# Exécutez ces filtres SDK sur le graphe de l'étape 1

Requete du SDK

for query in (
    {'admin_group': '0x00000002'},
    {'admin_group': '0x00000004'},
    {'temetric__gt': 30},
    {'unreserved_bw_7__lt': 1_000_000},
    {'is_te_link': False},
):
    result = graph.edges_list(per_page=200, **query)
    print(query, result['pagination']['total'])

Sortie du SDK

{'admin_group': '0x00000002'} 10
{'admin_group': '0x00000004'} 2
{'temetric__gt': 30} 4
{'unreserved_bw_7__lt': 1000000} 2
{'is_te_link': False} 2
Observations requises Faits a confirmer avant de continuer
  • Huit arêtes sur r10-r100, r100-r110 et r100-r111 portent des données SRLG ; les deux liens parallèles r10-r100 conservent leurs propres groupes.
  • La valeur texte is_te_link='yes' est rejetée avec HTTP 400 : Wrong type, expected 'boolean'.

11. Enregistrer le chemin sans contrainte

Calculez le chemin sans contrainte de r10 à r14 et conservez-le comme référence pour les exercices suivants. Chaque nouvelle contrainte pourra ainsi être comparée à un chemin connu.

Le chemin de référence est r10 r100 r110 r14, avec un coût de 30. Il emprunte le lien parallèle eth2 entre r10 et r100.

Commande

Ne changez aucun routeur ; exécutez le calcul CSPF de référence.

Requete du SDK

print(graph.cspf_path('r10', 'r14'))

Sortie du SDK

{'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
Observations requises Faits a confirmer avant de continuer
  • Les quatre étapes suivantes déplaceront ce chemin avec une seule contrainte à la fois.

12. Éviter les groupes de risque partagé

Utilisez srlg_exclude pour contourner un lien ou un conduit partagé en maintenance. Le chemin de référence r10 r100 r110 r14 coûte 30 : son premier saut appartient au SRLG 300 et les deux premiers sauts parallèles partagent le SRLG 100.

Exclure 300 conserve les mêmes routeurs mais sélectionne eth1, pour un coût de 35. Exclure 100 donne r10 r11 r100 r110 r14, coût 40 ; exclure 200 donne r10 r100 r101 r111 r14, coût 45 ; exclure 100 et 200 donne r10 r11 r101 r111 r14, coût 50.

Commande

Modifiez uniquement la liste `srlg_exclude`.

Requete du SDK

for groups in ([300], [100], [200], [100, 200]):
    print(groups, graph.cspf_path('r10', 'r14', srlg_exclude=groups))

Sortie du SDK

[300] {'cost': 35, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
[100] {'cost': 40, 'path': ['r10', 'r11', 'r100', 'r110', 'r14'], 'reason': ''}
[200] {'cost': 45, 'path': ['r10', 'r100', 'r101', 'r111', 'r14'], 'reason': ''}
[100, 200] {'cost': 50, 'path': ['r10', 'r11', 'r101', 'r111', 'r14'], 'reason': ''}
Observations requises Faits a confirmer avant de continuer
  • La contrainte retire des liens ; elle ne modifie pas la métrique IGP.

13. Choisir par bande passante et priorité

Demandez un tunnel de 8 Mbit avec différentes priorités d'établissement. Le chemin de référence r10 r100 r110 r14 coûte 30, mais r100-r110 n'annonce que 4 Mbit dans le pool de priorité 7 et 10 Mbit aux priorités 0 à 3.

À la priorité 7, CSPF déplace le tunnel vers r10 r100 r111 r14, coût 40. À la priorité 0, il conserve r10 r100 r110 r14, coût 30. Une demande de 20 Mbit ne tient sur aucun chemin disponible et renvoie un refus de contrainte.

Commande

Modifiez `bandwidth` et `setup_priority` dans la requête CSPF.

Requete du SDK

for priority in (7, 0):
    print(priority, graph.cspf_path('r10', 'r14', bandwidth='8M', setup_priority=priority))
print(graph.cspf_path('r10', 'r14', bandwidth='20M'))

Sortie du SDK

7 {'cost': 40, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
0 {'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
{'cost': None, 'path': [], 'reason': 'no path satisfies the requested constraints'}
Observations requises Faits a confirmer avant de continuer
  • La priorité choisit le pool de bande passante annoncé, mais ne change pas le coût IGP.

14. Calculer avec la métrique TE

Recalculez le chemin avec metric_type='te' après avoir vérifié que chaque lien candidat porte une métrique TE. Le chemin IGP r10 r100 r110 r14 coûte 30, mais r100-r110 a une métrique TE de 100 et r100-r111 une métrique TE de 10.

CSPF sélectionne r10 r100 r111 r14 avec un coût TE de 30. Le même chemin a un coût IGP de 40, ce qui montre l'effet de la métrique choisie.

Commande

Utilisez `metric_type='te'` sans modifier les routeurs.

Requete du SDK

print(graph.cspf_path('r10', 'r14', metric_type='te'))

Sortie du SDK

{'cost': 30, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
Observations requises Faits a confirmer avant de continuer
  • Sans métrique TE, le calcul utilise la métrique IGP de ce lien.

15. Inclure ou exclure des bits admin group

Utilisez les bits de groupe administratif pour imposer ou éviter des classes de liens. Le bit 1 désigne gold et le bit 2 red. Le chemin sans contrainte de r13 à r15 est r13 r100 r110 r15, coût 30.

Imposer gold avec admin_include_all=['1'] remplace ce chemin par r13 r101 r111 r15, coût 45 ; de r10 à r14, cela donne r10 r101 r111 r14, coût 55. Éviter red avec admin_exclude_any=['2'] donne r13 r100 r111 r15, coût 40.

Commande

Passez des numéros de bit, pas les masques hexadécimaux.

Requete du SDK

print(graph.cspf_path('r13', 'r15', admin_include_all=['1']))
print(graph.cspf_path('r10', 'r14', admin_include_all=['1']))
print(graph.cspf_path('r13', 'r15', admin_exclude_any=['2']))

Sortie du SDK

{'cost': 45, 'path': ['r13', 'r101', 'r111', 'r15'], 'reason': ''}
{'cost': 55, 'path': ['r10', 'r101', 'r111', 'r14'], 'reason': ''}
{'cost': 40, 'path': ['r13', 'r100', 'r111', 'r15'], 'reason': ''}
Observations requises Faits a confirmer avant de continuer
  • Le bit 1 est 0x00000002, le bit 2 0x00000004 ; le bit 0 est le moins significatif.

16. Limiter CSPF à un niveau IS-IS

Définissez level=1 ou level=2 pour calculer dans une seule topologie IS-IS et vérifiez que les deux extrémités y appartiennent. Entre r30 et r31, le chemin direct de niveau 1 coûte 10, tandis que le chemin de niveau 2 r30 r10 r11 r31 coûte 40.

Le chemin r130-r131 coûte 10 au niveau 1 mais renvoie src/dst not found au niveau 2. La topologie combinée contient un chemin r130-r15 de coût 50, mais aucun niveau individuel ne le contient, car il franchit la limite entre les niveaux.

Commande

Ajoutez `level=1` ou `level=2` à la requête CSPF.

Requete du SDK

for level in (None, 1, 2):
    print(level, graph.cspf_path('r30', 'r31', level=level))
print(graph.cspf_path('r130', 'r131', level=2))

Sortie du SDK

None {'cost': 10, 'path': ['r30', 'r31'], 'reason': ''}
1 {'cost': 10, 'path': ['r30', 'r31'], 'reason': ''}
2 {'cost': 40, 'path': ['r30', 'r10', 'r11', 'r31'], 'reason': ''}
{'cost': None, 'path': [], 'reason': 'src/dst not found'}
Observations requises Faits a confirmer avant de continuer
  • Un graphe ancien sans données par niveau retourne HTTP 422 et isis_level_calculation_unavailable ; il faut le recharger.

17. Distinguer contrainte impossible et endpoint absent

Lisez reason lorsque CSPF renvoie un path vide. La demande de 20 Mbit de l'étape 5 a des extrémités valides, mais aucun chemin n'offre assez de bande passante ; une demande de niveau 1 entre r130 et r15 n'a aucune topologie de niveau unique contenant les deux extrémités.

La demande de bande passante renvoie no path satisfies the requested constraints ; la demande au mauvais niveau renvoie src/dst not found. Ces échecs exigent des corrections différentes.

Commande

Répétez les requêtes refusées des étapes 5 et 8.

Requete du SDK

print(graph.cspf_path('r10', 'r14', bandwidth='20M'))
print(graph.cspf_path('r130', 'r15', level=1))

Sortie du SDK

{'cost': None, 'path': [], 'reason': 'no path satisfies the requested constraints'}
{'cost': None, 'path': [], 'reason': 'src/dst not found'}
Observations requises Faits a confirmer avant de continuer
  • Le premier message demande de relâcher bandwidth, affinity ou SRLG ; le second signale des endpoints absents.

18. Modifier un lien et recalculer

Modélisez la maintenance de r100-r110 sans modifier les routeurs. Sur une copie jetable du graphe, utilisez update_edge pour ajouter le SRLG 999 à l'arête de niveau 2, recalculez en excluant ce groupe, puis effacez le SRLG.

Exclure 999 remplace r10 r100 r110 r14, coût 30, par r10 r100 r111 r14, coût 40 ; effacer le SRLG restaure le chemin de référence. Une arête propre au niveau 2 refuse isis_level=1. Comme replace_edge supprime les données par niveau, une requête CSPF avec un niveau explicite renvoie HTTP 422 après un remplacement complet.

Commande

# Utilisez un graphe jetable : la modification est persistante
# Trouvez d'abord l'id de l'arête avec edges_list(include=['edge_key'])

Requete du SDK

edge = next(e for e in graph.edges_list(include=['edge_key'], per_page=200)['items']
             if e['src'] == 'r100' and e['dst'] == 'r110')
print(graph.update_edge(edge['id'], isis_level=2, srlg=[999]))
print(graph.cspf_path('r10', 'r14', srlg_exclude=[999]))
print(graph.update_edge(edge['id'], isis_level=2, srlg=[]))
print(graph.cspf_path('r10', 'r14'))

Sortie du SDK

... srlg: [999] ...
{'cost': 40, 'path': ['r10', 'r100', 'r111', 'r14'], 'reason': ''}
... srlg: [] ...
{'cost': 30, 'path': ['r10', 'r100', 'r110', 'r14'], 'reason': ''}
Observations requises Faits a confirmer avant de continuer
  • update_edge ne modifie que les champs fournis ; replace_edge réécrit toute l'arête. N'utilisez ni l'un ni l'autre sur le graphe de référence à conserver.

19. Placer des LSP et vérifier la bande passante restante

Ajoutez quatre tunnels de 4 Mbit de r10 à r14, un par un, et examinez la bande passante résiduelle et le chemin choisi après chaque placement. Avant le premier placement, le pool de priorité 7 de 4 Mbit du chemin de référence r10 r100 r110 r14 est disponible.

T1 utilise r10 r100 r110 r14, coût 30 ; T2 utilise r10 r100 r111 r14, coût 40 ; T3 utilise l'autre lien r10-r100, coût 45 ; et T4 utilise r10 r11 r101 r110 r14, coût 55. Supprimez tous les LSP de test à la fin de l'exercice.

Commande

# Ne modifiez pas les routeurs : créez quatre tunnels RSVP-TE de 4 Mbit avec le SDK

Requete du SDK

for name in ('T1', 'T2', 'T3', 'T4'):
    lsp = graph.add_lsp({
        'name': name, 'src': 'r10', 'dst': 'r14', 'bandwidth': '4M',
        'paths': {'primary': {'role': 'primary', 'bandwidth': '4M'}},
    })
    primary = lsp['paths']['primary']
    print(name, primary['path'], primary['cost'])
print(graph.edges_list(include=['lsp_left_bw', 'lsps'], per_page=200)['items'])
graph.delete_lsps()

Sortie du SDK

T1 ['r10', 'r100', 'r110', 'r14'] 30
T2 ['r10', 'r100', 'r111', 'r14'] 40
T3 ['r10', 'r100', 'r110', 'r14'] 45
T4 ['r10', 'r11', 'r101', 'r110', 'r14'] 55
... lsp_left_bw_7 et lsps pour chaque arête ...
{'deleted': 4}
Observations requises Faits a confirmer avant de continuer
  • Après chaque placement, utilisez edges_list(include=['lsp_left_bw', 'lsps']) pour voir quel pool a provoqué le changement de chemin suivant.
  • Appelez toujours delete_lsps() pendant le nettoyage ; le placement de LSP modifie le graphe stocké.
Topolograph 2.72.1 📣 Rejoignez la communauté !