Hlavní navigace

Mikrotik: skriptování v RouterOS

5. 10. 2012
Doba čtení: 9 minut

Sdílet

RouterOS je dnes základ pro nezanedbatelné množství větších i menších sítí. Jednou z jeho vlastností jsou skripty, kterými lze dynamicky libovolně měnit konfiguraci routeru. Operační systém RouterOS je každým coulem přizpůsoben svému účelu, a tak skriptování v něm nemusí být jasné na první pohled.

Jednou z možností, jak RouterOS nakonfigurovat, je použití SSH či telnetu. Pokud jste tento přístup někdy zkoušeli, tak víte, že konfigurace je rozdělena do stromové struktury a v té se nacházejí příkazy pro práci s různými vlastnostmi routeru. V článku budu předpokládat, že jste již s touto strukturou pracovali a víte, kde se co přibližně nachází.

Příkazy, které hledáte a spouštíte v adresářové struktuře, zadáváte do interpretu v RouterOS, který si je překládá podle svého tak, aby se router začal chovat dle vašeho přání. Jde o stejný princip jako v Linuxu, kde když spustíte příkazovou řádku, také se vám objeví nejčastěji BASH, DASH nebo jiný shell, ve kterém si můžete spustit jeden program a pracovat v něm nebo rovnou napsat skript využívající vlastnosti daného shellu a třeba i několika dalších programů. Na rozdíl od BASHe a jeho alternativ je skriptování v RouterOS odlišné od mainstreamových skriptovacích jazyků, a to hlavně kvůli přizpůsobení struktuře konfiguračních voleb.

Kromě toho je potřeba počítat s dalšími omezeními. Třeba data, se kterými můžete pracovat, se musí nacházet na routeru, kde běží skript. Není možné přistupovat do databáze nebo třeba do jiného routeru, resp. na běžném linuxovém stroji uděláte takový skript mnohem rychleji a pohodlněji. Na problém také narazíte, pokud začnete uvažovat o tom, že skriptu chcete předat nějaké parametry. V takovém případě je nutné využít globální proměnné.

Ale Mikrotik by se neobtěžoval se skriptováním, kdyby si nemyslel, že ho uživatelé využijí. I když není moc možností, jak spolupracovat s okolím, pořád je to bezkonkurenční nástroj na aktivní reakci na různé události. Provádění je rychlé, a tak není problém spouštět skript, který hlídá kritické hodnoty, jednou z pár sekund. Navíc je možné reagovat na několik událostí, třeba na cizí DHCP server na stejné síti nebo na stav nějakého aktivního prvku.

Střední cestou mezi skriptováním v RouterOS a nebo na externím linuxovém PC/routeru je využití funkce Metarouter, což je virtualizace, ve které lze spustit na deskách s RouterOS jiný operační systém, třeba OpenWRT. Pokud má router dostatečný výkon, tak v takovém případě lze využít všech flexibilností Linuxu a zároveň zbytečně nezvedat množství aktivních prvků.

A jdeme na to

Základem pro každého programátora v RouterOS je stránka Scripting na wiki.mikrotik.com. Zde je popsané všechno možné, co jazyk nabízí, ale chybí komplexnější příklady, na kterých by uživatel mohl spojit vlastnosti s reálným světem. Takové najdete na stránce Scripting-examples na stejné wiki.

V RouterOS mají všechny příkazy stejnou syntaxi. Všem se předávají parametry stejným způsobem a i stejným způsobem se volí správný příkaz. Syntaxe je následující:

[prefix] [path] command [uparam] [param=[value]] .. [param=[value]] 

Kde:

[prefix] - může být ":" nebo "/" což značí, zda se jedná o globální příkaz či cestu
[path] - cesta ve stromové struktuře, prefix "/" pak značí, že cesta začíná v rootu
command - příkaz dostupný ve vybrané úrovni stromu
[uparam] - bezejmenné parametry (často třeba ID položky, *set 0 ..*)
[params] - pojmenované parametry 

RouterOS má příkazy schované ve stromové struktuře a disponuje několik globálními příkazy pro práci s proměnnými, poli, logování, hledání, práci s řetězci a další. Jejich seznam najdete taktéž na wiki.

Volání různých příkazů lze do sebe zanořovat. Například podmínka „if“ není také nic jiného než příkaz mimo jiné s parametrem do, do kterého se dá vložit jeden či více příkazů. Ale nepředbíhejme.

Proměnné

Pomocí globálních příkazů :local a :global lze vytvořit proměnné s různým obsahem nebo změnit již existující. Jak název napovídá, první příkaz slouží pro vytvoření lokální proměnné platné pouze v daném skriptu či jmenném prostoru (viz níže) a pro vytvoření globální proměnné dostupné ze všech skriptů. Oba příkazy se používají následovně:

:global a 1
:put $a 

Když oba řádky spustíte na stroji s RouterOS, bude do proměnné a uložena jednička a obratem pomocí příkazu :put vypsána na standardní výstup. Globální proměnnou jsem v tomto případě zvolil proto, že na příkazové řádce mají lokální proměnné platnost jen v rámci jednoho volání. Zjednodušeně řečeno v rámci „jednoho řádku“.

[admin@lukas] > :local b 1
[admin@lukas] > :put $b
syntax error (line 1 column 7) 

Když ale použijeme středník pro oddělení :local a :put na jednom řádku, bude se program chovat správně:

[admin@lukas] > :local b 1; :put $b
1 

Když má být několik příkazů propojeno, používají se k tomu hranaté závorky. Třeba tímto způsobem se vypíše výsledek příkazu :len

:put [:len {1;2;3;4}] 

V případě, že chcete vytisknout řetězec, můžete ho zapsat jak s uvozovkami, tak bez nich (když neobsahuje mezery):

[admin@lukas] > :put ABC
ABC
[admin@lukas] > :put "DEF"
DEF 

Spojování řetězců se provádí tečkou a musí být v závorkách:

[admin@lukas] > :put ("DEF" . "GHI")
DEFGHI 

Podobně lze provádět matematické operace:

[admin@lukas] > :put 1+2
1+2 

Kde závorky nemusí být, pokud není mezi výrazy mezera.

Komentáře začínají znakem „#“, který nesmí být odsazen od začátku řádku.

# Vypis jednicku - dobre
:put 1
    #Vypis dvojku - spatne
:put 2 

Globální příkazy

Výše byla zmíněna existence globálních příkazů, z nichž jsme si už čtyři ukázali. Je jich ale mnohem víc, z nich ty nejdůležitější najdete v následující tabulce:

Příkaz Syntaxe Popis
:local :local <proměnná> [<hodnota>] Definuje lokální proměnnou
:global :global <proměnná> [<hodnota>] Definuje globální proměnnou
:put :put <výraz> Vypíše *výraz* na standardní výstup
:beep :beep <frekvence> <delka> Udělá beep o určité frekvenci a délce
:pick :pick <proměnná> <začátek>[<konec>] Vybere z *proměnné* typu pole prvky od *začátku* po *konec*
:log :log <topic> <zpráva> Do systémového logu vloží *zprávu*, kde *topic* může být *debug*, *error*, *info* nebo *warning*
:resolve :resolve <doména> Přeložit *doménu* na adresu
:toarray/bool/id/ip/ip6/num/str/time :toarray <hodnota> Přetypování na pole, logický výraz, id, IP, IPv6, číslo, řetězec nebo čas
:error :error <zpráva> Ukončí skript a vypíše zprávu
:len :len <výraz> Vrací délku výrazu, což může být řetězec nebo pole
:delay :delay <čas> Nedělat po <čas> sekund
:set :set <proměnná> <hodnota> Nastavení hodnoty již deklarované proměnné.

Jmenné prostory

RouterOS při provádění skriptu bere ohled na jmenné prostory (scope). Jde o oblast v kódu, mimo kterou nezasahují lokální ani globální proměnné. Prostory mohou být do sebe zanořeny. Na nejvyšší úrovni je pak globální prostor, do kterého jsou zanořeny prostory spouštěných skriptů. To je také důvod, proč lokální proměnná definovaná v příkazovém řádku není dostupná při volání na jiném řádku, než byla definovaná.

Jmenné prostory se uzavírají složenými závorkami. Tady pozor, protože složené závorky se používají také pro pole, kde jsou jednotlivé hodnoty oddělené středníkem.

{
   :local a 3;
   {
      :local b 4;
      :put ($a+$b);
   }
#tento řádek hodí chybu, proměnná b neexistuje
   :put ($a+$b);
} 

Stejně by se kód zachoval, kdyby místo „:local b 4“ bylo „:global b 4“. Globální proměnné nelze v lokálním jmenném prostoru definovat. Pouze měnit hodnotu již existujících proměnných.

Podmínky

Pro rozhodování na základě různých podmínek se používá příkaz :if. Jednoduchá podmínka může vypadat třeba takto:

:local a 1
:if ( $a > 1 ) do={:put "A je vetsi jak 1"} \
 else={:put "A neni vetsi jak 1"} 

Všimněte si, že parametrům do a else je předáván jmenný prostor, který může obsahovat libovolně dlouhý kód včetně dalších vnořených podmínek.

Cykly

Podobně jako podmínky fungují cykly. Také jsou implementovány jako globální příkazy a to rovnou ve třech variantách. Prvním je cyklus do..while, který lze využít jak v pořadí while-do, tak do-while.

:local a 10
:while ( $a > 0) do={
    :put $a
    :set a ($a-1)
} 

Po spuštění dostaneme:

[admin@lukas] /system script> run test
10
9
8
7
6
5
4
3
2
1 

Když do a while otočíme, výsledek je stejný, jen kód vypadá trochu jinak.

:local a 10
:do {
    :put $a
    :set a ($a-1)
} while=( $a > 0 ) 

V obou případech se podmínka ověřuje na začátku cyklu.

Dalším typem cyklu je for, jehož funkci nejlépe pochopíte z příkladu.

:for i from=1 to=10 step=2 do={ :put $i }

[admin@lukas] /system script> run test
1
3
5
7
9 

Posledním a nejužitečnějším je cyklus foreach, který slouží k procházení polí.

:foreach i in={1;2;3} do={ :put $i }

[admin@lukas] /system script> run test
1
2
3 

Práce se stromovou strukturou

Většinu času se nebudete omezovat jen na data, která jste do skriptu zadali, ale budete potřebovat získat data z útrob stromové struktury routeru. V takovém případě vám pomohou příkazy get a find. Možností, jak získat data, je více a nejjednodušší je znát místo, kde se údaj nachází. Takto třeba získáme IP adresu prvního rozhraní.

:put [/ ip address get 0 address] 

Pokud ale chceme adresy všech rozhraní, můžeme použít foreach cyklus.

:foreach i in=[/ ip address find] do={
    :put [/ ip address get $i address]
} 

Výraz / ip address find vrátí pole interních identifikačních čísel všech IPv4 adres, které se v routeru nacházejí a pomocí nich můžeme vypsat jednotlivé adresy.

Někdy se může stát, že známe adresu, ale neznáme rozhraní, na kterém se nachází. Stačí k tomu vnořit několik příkazů do sebe:

:put [
    / ip address get [/ ip address find \
    address="192.168.88.1/24"] interface
] 

V obou případech sloužil příkaz find pro filtrování položek, o které máme zájem. Příkaz get se pak postaral o to, abychom získali požadovanou hodnotu. Obdobně se dá použít příkaz set pro nastavení hodnoty a vůbec vše, na co jste zvyklí i bez znalosti skriptování.

Spuštění skriptu

Skript je psát přímo v příkazovém řádku. Nejdříve ho vytvoříme:

[admin@lukas] > / system script
[admin@lukas] /system script> add name=testovaci_skript 

A pak upravíme zdrojový kód:

[admin@lukas] /system script> edit testovaci_skript source 

Jak jsme si řekli výše, skript lze spustit jako reakci na nějakou událost, lze ho načasovat nebo spustit ručně. Načasování bere RouterOS také jako událost. Když spuštění skriptu načasujete, spustí se při události vyvolané v nastavený čas. Nastavení se provádí v / system scheduler. Aby byl skript test spuštěn každých deset minut, spustíme toto:

/ system scheduler add name=testovaci_skript \
on-event=test interval=10m 

V případě že nenastavíme interval, můžeme zvolit konkrétní čas a datum:

add name=testovaci_skript on-event=test start-time=11:00:00 start-date=oct/10/2012 

Jednou z dalších událostí je netwatch, který spouští skripty když zjistí, že nějaká IP adresa změnila stav, tedy že je či není dostupná. Netwatch se nachází v / tool netwatch. Příklad použití může vypadat takto:

/ tool netwatch add host=192.168.88.3 \
  up-script=ip_up down-script=ip_down interval=5m 

V případě, že IP adresa 192.168.88.3 nebude dostupná, spustí se skript ip_down, v opačném případě ip_up.

root_podpora

Ruční spuštění skriptu můžeme provést v menu / system script příkazem run.

Shrnutí

Skriptování v RouterOS dokáže v některých situacích vytrhnout trn z paty, ale nejde o komplexní nástroj, který by dokázal vše. Co všechno vytvořili za skripty ostatní, na to se můžete podívat do uživatelských skriptů na mikrotik wiki. Za zmínku stojí například tento možná až šílený příklad fakturačního systému implementovaného pouze pomocí skriptování v RouterOS.

Byl pro vás článek přínosný?

Autor článku

Adam Štrauch je redaktorem serveru Root.cz a svobodný software nasazuje jak na desktopech tak i na routerech a serverech. Ve svém volném čase se stará o komunitní síť, ve které je již přes 100 členů.