Tclsh + CISCO
Na urządzeniach z CISCO IOS oraz CISCO IOS-XE zainstalowany jest interpreter języka skryptowego TCL. Dzięki czemu możemy tworzyć i wykonywać skrypty bezpośrednio na urządzeniach CISCO. Jest to bardzo przydatne i w poniższych przykładach pokażę do czego można tego użyć.
Opracowano kila metod uruchamiani skryptów TCL na oprogramowaniu CISCO IOS. Powłoka TCLSH może zostać uruchomiona, a polecenia mogą być wprowadzane linijka po linijce. Powłoka TCLSH została wprowadzona w wersji IOS 12.3(2)T. W celu wejścia do tclsh należy wydać komendę tclsh:
R1#tclsh R1(tcl)#
Wyjście z tclsh odbywa się przy pomocy komendy tclquit:
R1(tcl)#tclquit R1#
Po wprowadzeniu komend Tcl są one wysyłane do interpretera Tcl. Jeśli polecenie są rozpoznawane jako poprawne polecenie Tcl to jest ono wykonywanie. Jeśli polecenie nie jest rozpoznawanym poleceniem Tcl to jest ono wysyłane do parsera CLI Cisco IOS. Jeśli polecenie nie jest rozpoznawane ani jako Tcl ani jako polecenie Cisco IOS to wyświetlane są dwa komunikaty o błędach:
R1(tcl)#command invalid command name "command" ^ % Invalid input detected at '^' marker.
Powyżej widać te dwa komunikaty o błędach po wpisaniu komendy command. Spróbujmy teraz wpisać komendę cisco, np. show ip int brief:
R1(tcl)#show ip int brief Interface IP-Address OK? Method Status Protocol FastEthernet0/0 unassigned YES unset administratively down down FastEthernet1/0 unassigned YES unset administratively down down FastEthernet1/1 unassigned YES unset administratively down down
a następnie wykonajmy kilka komend Tcl:
R1(tcl)#set a 10 10 R1(tcl)#set b 20 20 R1(tcl)#set c [expr $a + $b] 30
Poradnik z podstawowymi komendami języka skryptowego Tcl znajduje się pod tym linkiem.
Wstępnie napisany skrypt Tcl można utworzyć również poza urządzeniem, następnie przekopiować do pamięci flash urządzenia i wtedy go uruchomić.
Komendy Cisco IOS w Tcl w trybie uprzywilejowanym możemy wydawać w następujący sposób:
(tcl)#exec cisco_ios_command – komenda w trybie uprzywilejowanym
R1(tcl)#exec show inventory NAME: "Chassis", DESCR: "Cisco 7206VXR, 6-slot chassis" PID: CISCO7206VXR , VID: , SN: 4279256517 NAME: "NPE400 0", DESCR: "Cisco 7200VXR Network Processing Engine NPE-400" PID: NPE-400 , VID: , SN: 11111111 NAME: "module 0", DESCR: "I/O FastEthernet (TX-ISL)" PID: C7200-IO-FE-MII/RJ, VID: , SN: 4294967295 NAME: "module 1", DESCR: "Dual Port FastEthernet (RJ45)" PID: PA-2FE-FX , VID: , SN: XXX00000000 NAME: "Power Supply 0", DESCR: "Cisco 7200 AC Power Supply" PID: PWR-7200-AC , VID: , SN: NAME: "Power Supply 1", DESCR: "Cisco 7200 AC Power Supply" PID: PWR-7200-AC , VID: , SN:
(tcl)#ios_config cisco_ios_command – komenda w trybie konfiguracji
R1(tcl)#ios_config "int fa0/0" "no sh" R1(tcl)# *Nov 10 13:08:21.887: %SYS-5-CONFIG_I: Configured from console by console R1(tcl)# *Nov 10 13:08:23.799: %LINK-3-UPDOWN: Interface FastEthernet0/0, changed state to up *Nov 10 13:08:24.799: %LINEPROTO-5-UPDOWN: Line protocol on Interface FastEthernet0/0, changed state to up R1(tcl)#
Skrypt 1
Skrypt 1 będzie klasycznym skryptem, wykonującym komendę ping dla zadanych adresów IP. W moim przykładzie posłużę się następującą topologią:
R1 – fa0/0 – 192.168.1.1/24
R2 – fa0/0 – 192.168.1.2/24
R3 – fa0/0 – 192.168.1.3/24
R4 – fa0/0 – 192.168.1.4/24
R5 – fa0/0 – 192.168.1.5/24
Skrypt stwórzmy na routerze R1. Skrypt będzie wykonywał polecenie ping na adresy interfejsów fa0/0 dla każdego z routerów od R1 do R5. W skrypcie skorzystamy z pętli foreach:
foreach <zmienne> <lista> [<zmienne lista>...] <skrypt>
R1(tcl)#foreach address { +>(tcl)#192.168.1.1 +>(tcl)#192.168.1.2 +>(tcl)#192.168.1.3 +>(tcl)#192.168.1.4 +>(tcl)#192.168.1.5} {ping $address +>(tcl)#} Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 192.168.1.1, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/4 ms Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 192.168.1.2, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 8/10/16 ms Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 192.168.1.3, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 8/13/20 ms Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 192.168.1.4, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 16/18/20 ms Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 192.168.1.5, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 4/8/12 ms R1(tcl)#
Jak możemy zaobserwować powyżej skrypt wykonał się poprawnie. Przejdźmy teraz do drugiego przykładu.
Skrypt 2
Skrypt 2 będzie połączeniem komend Tcl oraz wyrażeń regularnych. Spróbujemy z routera R1 wyciągnąć z komendy show version informację o numerze seryjnym. Wyrażenia regularne do wyciągania informacji z komend Cisco IOS są dokładniej opisane w tym wpisie.
Zacznijmy pisać skrypt:
Wydajmy polecenie (tcl)#exec show ver
... Cisco 7206VXR (NPE400) processor (revision A) with 491520K/32768K bytes of memory. Processor board ID 4279256517 R7000 CPU at 150MHz, Implementation 39, Rev 2.1, 256KB L2 Cache 6 slot VXR midplane, Version 2.1 ...
Numer seryjny urządzenia możemy znaleźć w linijce zaczynającej się od Processor board ID. W moim przypadku jest to 4279256517. Wklejmy teraz tą linijkę do regex101 i napiszmy wyrażenie regularne:
Nasze wyrażenie regularne będzie mieć postać:
Processor\sboard\sID\s(\S+)
Skrypt wyciągający tą informację będzie wyglądał następująco:
R1(tcl)#set sh_ver [exec show ver] R1(tcl)#regexp {Processor\sboard\sID\s(\S+)} $sh_ver line_with_serial serial 1 R1(tcl)#puts $serial 4279256517
Prześledźmy ten skrypt linijka po linijce. Pierwsza linijka przypisuję do zmiennej sh_ver wynik komendy show version. W drugiej linijce sprawdzamy dopasowanie (występujące między nawiasami po slowie regexp) tekstu ze zmiennej sh_ver a następnie wynik zapisujemy do zmiennych:
line-with-serial -> całe dopasowanie
serial -> dopasowanie z nawiasu(), czyli nasz numer seryjny
Jeśli dopasowanie zostało znalezione to powyższa komenda zwraca wynik 1, jeśli nie to zwraca wynik 0.
Ostatnia linijka R1(tcl)#puts $serial wyświetla numer seryjny.