Писати скрипти для Mikrotik RouterOS - це просто

RouterOS — мережева операційна система на базі Linux. Дана операційна система призначена для установки на апаратні маршрутизатори Mikrotik RouterBoard. Також дана система може бути встановлена на ПК (або віртуальну машину), перетворюючи його в маршрутизатор. Спочатку досить багата функціоналом ОС ні ні так і здивує відсутністю якої-небудь потрібної фішки з коробки. На жаль, доступ до Linux-оточенню дуже сильно обмежений, тому, «це є під Linux» абсолютно не рівнозначно «це є в RouterOS». Але не треба впадати у відчай! Ця система надає кілька можливостей для розширення свого функціоналу. Перша — найпростіша і нативна — це можливість писати скрипти на вбудованій мові.
В даній статті, в якості прикладу буде розглянутий скрипт, що перетворює DNS-імена в списки IP-адрес (address lists).
Навіщо він може бути потрібен? Багато сайтів використовують Round Robin DNS для розподілу навантаження (а деякі і не тільки для цього). Щоб керувати доступом до такого сайту (створити правило маршрутизації або фаєрвола) нам потрібні всі IP-адреси, відповідні цьому доменному імені. Більш того список IP-адрес по закінченні часу життя даної DNS-записи (у даному випадку мова йде про A-записи) може бути виданий абсолютно новий, тому інформацію доведеться періодично оновлювати. На жаль в RouterOS не можна створити правило
блокувати всі TCP з'єднання на порт 80 за адресою example.com
на місці example.com має бути IP-адресу, але як ми вже зрозуміли, example.com відповідає не один, а кілька IP-адрес. Щоб нас визволити від мук створення і підтримки купи однотипних правил, розробники RouterOS дали можливість створювати правило так:
блокувати всі TCP з'єднання на порт 80 за будь-якою адресою списку з ім'ям DenyThis
Справа залишилася за малим — автоматично формувати цей самий список. Хто ще не втомився від моєї писанини запрошую під хабракат.

Відразу наведу текст скрипта, далі піде його покроковий розбір
:local DNSList {"example.com";"non-exist.domain.net";"server.local";"hostname"}
:local ListName "MyList"
:local DNSServer "8.8.8.8"
:foreach addr in $DNSList do={
:do {:resolve server=8.8.8.8 $addr} on-error={:debug log ("failed to resolve $addr")}
}
/ip firewall address-list remove [find where list~$ListName]
/ip dns cache all
:foreach i in=[find type="A"] do={
:local bNew true
:local cacheName [get $i name]
:local match false
:foreach addr in=$DNSList do={
:if (:typeof [:find $cacheName $addr] >= 0) do={
:set $true match
}
}
:if ( $match ) do={
:local tmpAddress [/ip dns cache get $i address]
:if ( [/ip firewall address-list find ] = "") do={
:debug log ("added entry: $[/ip dns cache get $i name] IP $tmpAddress")
/ip firewall address-list add address=$tmpAddress list=$ListName comment=$cacheName
} else={
:foreach j in=[/ip firewall address-list find ] do={
:if ( [/ip firewall address-list get $j address] = $tmpAddress ) do={
:set bNew false
}
}
:if ( $bNew ) do={
:debug log ("added entry: $[/ip dns cache get $i name] IP $tmpAddress")
/ip firewall address-list add address=$tmpAddress list=$ListName comment=$cacheName
}
}
}
}



Текст скрипта потрібно додати в репозиторій скриптів, що знаходиться в розділі /system scripts.
Скрипт виконується порядково. Кожен рядок має наступний синтаксис:
[prefix] [path] command [uparam] [param=[value]] .. [param=[value]]
[prefix] — ":" — для глобальних команд, з символу "/" починається командний рядок, яка буде виконуватися щодо кореня конфігурації, префікс може бути відсутнім, тоді командний рядок виконується щодо поточного розділу конфігурації;
[path] — шлях до потрібного розділу конфігурації, за яким відбувається перехід перед виконанням команди;
command — безпосередньо дію, виконувану командним рядком;
[uparam] — безіменний параметр команди;
[param=[value]] — іменовані параметри та їх значення.

Отже, насамперед, визначимо параметри роботи скрипта у вигляді змінних. Змінна оголошується командами :local :global, відповідно отримуємо локальну змінну, доступну тільки в межах своєї зони видимості, або глобальну мережу, яка додається в список змінних оточення ОС буде доступна звідки завгодно. Локальні змінні живуть, поки виконується їх зона видимості, глобальні — поки ми не видалимо їх.

:local DNSList {"example.com";"non-exist.domain.net";"server.local";"hostname"}
:local ListName "MyList"
:local DNSServer "8.8.8.8"
Мінлива DNSList містить масив доменів, з яким ми хочемо працювати. Змінна ListName містить рядок, якою буде називатися отриманий address-list. Змінна DNSServer — містить адресу DNS-сервера, який буде використовуватися для отримання інформації про записи доменів.

:foreach addr in $DNSList do={
:do {:resolve server=$DNSServer $addr} on-error={:debug log ("failed to resolve $addr")}
}
В циклі «для кожного» обійдемо масив доменів і отрезолвим їх IP-адреси. Конструкція
:do {command} on-error={command}
служить для відлову runtime-помилок. Якщо не використовувати її, то скрипт може перерветься при помилці резолва неіснуючого або помилкового адреси.

/ip firewall address-list remove [find where list~$ListName]

Перейдемо до розділу конфігурації /ip firewall address-list і видалимо записи, в яких назва списку містить значення змінної $ListName. Конструкція з квадратних дужок дозволяє в рамках поточної команди виконати іншу, а результат виконання передати поточній у вигляді параметра.

/ip dns cache all
:foreach i in=[find type="A"] do={
перейдемо до розділу конфігурації /ip dns cahe all. Там містяться DNS-кеш роутера у вигляді таблиці Name — Type — Data — TTL. Виконаємо відбір по типу — нам потрібні тільки A-записи. І результат відбору обійдемо в циклі «для кожного». Це і буде головним циклом нашого скрипта.

:local bNew true
:local cacheName [get $i name]
:local match false

Створимо змінні, оновлювані в кожному циклі: два прапори — bNew, виключає дублювання, match, показує, входить поточна запис кеша в наш список доменів; змінна cacheName містить поле Name поточного запису кеша, тобто домен.

:foreach addr in=$DNSList do={
:if (:typeof [:find $cacheName $addr] >= 0) do={
:set $true match
}
}

Обійдемо список доменів і для кожного перевіримо, чи міститься в рядку cacheName підрядок у вигляді домену з цього списку. Чому не використовувати порівняння на рівність?Дуже просто — логіка скрипта припускає що під-домени повинні оброблятися так само як домени. Якщо ми хочемо блокувати социалочки, то має сенс блокувати не тільки основний домен, але і, наприклад, сервера віддають статику, картинки, скрипти, і знаходяться на під-доменах даного сайту. Так само це дозволить уникнути перерахування окремо доменів з «www» і без. То що, ці домени не потрапили в кеш при резолве — не страшно, оскільки вони можуть попасти туди при резолве браузером користувача (правда для цього потрібно, щоб DNS-запити користувача оброблялися в RouterOS). Якщо міститься, встановимо значення прапора match в true.

:if ( $match ) do={
:local tmpAddress [/ip dns cache get $i address]
:if ( [/ip firewall address-list find ] = "") do={
:debug log ("added entry: $[/ip dns cache get $i name] IP $tmpAddress")
/ip firewall address-list add address=$tmpAddress list=$ListName comment=$cacheName
} else={
:foreach j in=[/ip firewall address-list find ] do={
:if ( [/ip firewall address-list get $j address] = $tmpAddress ) do={
:set bNew false
}
}
:if ( $bNew ) do={
:debug log ("added entry: $[/ip dns cache get $i name] IP $tmpAddress")
/ip firewall address-list add address=$tmpAddress list=$ListName comment=$cacheName
}
}
}

У заключающем етапі якщо поточний адресу вимагає додавання (match встановлено в true), то ми його додаємо в список адрес. Коментар до запису додається буде містити домен, до якого вона належить. При цьому виконуємо кілька перевірок. Якщо address-list порожній, то додаємо відразу, якщо щось там є, перевіряємо, чи немає там вже запису з таким IP-адресою і якщо немає — додаємо.

Список адрес потрібно періодично оновлювати. Для цього в RouterOS є диспетчер завдань. Завдання можна додати з консолі або графічного інтерфейсу winbox
/system scheduler
add interval=5m name=MyScript on-event="/system run script MyScript" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive start-date=may/08/2014 \
start-time=10:10:00


Сценарії роботи зі списком адрес не обмежуються створенням правил в фаєрволі. Тому наведу кілька прикладів. Можна виконувати в консолі, можна додавати мишкою в winbox'е.
Чорний список:
/ip firewall filter 
add chain=forward protocol=tcp dst-port=80 address-list=DenyThis \
action=drop

Статичний маршрут до даних вузлів
/ip firewall mangle
add action=mark-routing chain=prerouting dst-address-list=AntiZapret \
in-interface=bridge_lan new-routing-mark=RouteMe

/ip route
add distance=1 gateway=172.16.10.2 routing-mark=RouteMe

Збір інформації про клієнтів
/ip firewall mangle
add action=add-src-to-address-list address-list=FUPer chain=prerouting \
dst-address-list=Pron log=yes log-prefix=critical


Список джерел:
Документація з написання скриптів
Прості приклади від розробників RouterOS
Скрипти, додані користувачами

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.