Андрей Смирнов
Время чтения: ~7 мин.
Просмотров: 10

Писать скрипты для Mikrotik RouterOS — это просто

  • Tutorial

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 DNSServers ( [ip dns get dynamic-servers], [ip dns get servers ], 8.8.8.8 ) :foreach addr in $DNSList do={      :foreach DNSServer in $DNSServers do={           :do {:resolve server=$DNSServer $addr} on-error={:log debug ("failed to resolve $addr on $DNSServer")}      } } /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 $match true        }     }     :if ( $match ) do={         :local tmpAddress [/ip dns cache get $i address]         :if ( [/ip firewall address-list find ] = "") do={             :log debug ("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={                 :log debug ("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 DNSServers ( [ip dns get dynamic-servers], [ip dns get servers ], 8.8.8.8 ) 

Переменная DNSList содержит массив доменов, с которым мы хотим работать. Переменная ListName содержит строку, которой будет называться полученный address-list. Переменная DNSServers — содержит массив адресов DNS-серверов, прописанных на роутере или полученных от провайдера при подключении, плюс «восьмёрки» на случай, если на роутере не используется служба DNS, который будет использоваться для получения информации о записях доменов.

:foreach addr in $DNSList do={      :foreach DNSServer in $DNSServers do={           :do {:resolve server=$DNSServer $addr} on-error={:log debug ("failed to resolve $addr on $DNSServer")}      } } 

В цикле «для каждого» обойдём массив доменов и отрезолвим их IP-адреса на каждом DNS-сервере на случай, если разные DNS отдают разные 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 $match true   } } 

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

:if ( $match ) do={   :local tmpAddress [/ip dns cache get $i address]   :if ( [/ip firewall address-list find ] = "") do={     :log debug ("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={       :log debug ("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 script run 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Скрипты, добавленные пользователямиUPD: специально по просьбе внёс изменения в скрипт, чтобы адреса DNS-серверов брались из системы.UPD 24.08.2016: заметил, что в новых версиях RouterOS (начиная с 6.36) появилась возможность указывать в адрес-листах DNS-имена. Так что теперь ценность данного скрипта лишь образовательная.Используемые источники:

  • https://habr.com/post/242143/

Рейтинг автора
5
Подборку подготовил
Максим Уваров
Наш эксперт
Написано статей
171
Ссылка на основную публикацию
Похожие публикации