📜 ⬆️ ⬇️

Writing scripts for Mikrotik RouterOS is just

RouterOS is a Linux based network operating system. This operating system is designed for installation on hardware routers Mikrotik RouterBoard. Also, this system can be installed on a PC (or virtual machine), turning it into a router. Initially, quite rich in OS functionality, no, no, and it will surprise you with the absence of some necessary chip from the box. Unfortunately, access to the Linux environment is very limited, therefore, “this is on Linux” is absolutely not equivalent to “this is in RouterOS”. But do not despair! This system provides several options for expanding its functionality. The first - the most simple and native - is the ability to write scripts in the embedded language.
In this article, as an example, we will consider a script that converts DNS names to lists of IP addresses (address lists).
Why might he be needed? Many sites use Round Robin DNS for load sharing (and some not only for this). To control access to such a site (to create a routing rule or firewall), we will need all the IP addresses corresponding to this domain name. Moreover, the list of IP addresses after the lifetime of this DNS record (in this case, this is an A-record) can be issued completely new, so the information will have to be periodically updated. Unfortunately you cannot create a rule in RouterOS
block all TCP connections on port 80 at example.com
in place of example.com there should be an IP address, but as we already understood, example.com corresponds not to one, but several IP addresses. To save us from the torment of creating and maintaining a heap of similar rules, the developers of RouterOS made it possible to create a rule like this:
block all TCP connections on port 80 to any address from the list with the name DenyThis
The case remains for the small - automatically generate this very list. Who else did not get tired of my scribbling invite under Habrakat.

Immediately give the text of the script, followed by a step by step analysis
: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 } } } } 



The script text must be added to the script repository located in the / system scripts section.
The script is executed line by line. Each line has the following syntax:
 [prefix] [path] command [uparam] [param=[value]] .. [param=[value]] 
[prefix] - ":" - for global commands, the command line starts with the "/" symbol, which will be executed relative to the configuration root, the prefix may be missing, then the command line is executed relative to the current configuration section;
[path] - path to the required configuration section, which is used to navigate before executing the command;
command - directly the action performed by the command line;
[uparam] - the nameless parameter of the command;
[param = [value]] - named parameters and their values.

So, first of all, we define the parameters of the script in the form of variables. The variable is declared by the commands: local and: global, respectively, we obtain a local variable that is available only within its visibility zone, or global, which is added to the list of OS environment variables and will be accessible from anywhere. Local variables live as long as their zone of visibility is fulfilled, global ones until we remove them.
')
 :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 ) 
The variable DNSList contains an array of domains that we want to work with. The variable ListName contains a string, which will be the resulting address-list. Variable DNSServers - contains an array of DNS server addresses assigned on the router or received from the provider when connecting, plus the “eights” in case the router does not use DNS, which will be used to obtain information about domain records.

 :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")} } } 
In the cycle “for everyone” we will go around the array of domains and cut off their IP addresses on each DNS server in case different DNS give different IPs. Design
 :do {command} on-error={command} 
serves to catch runtime errors. If you do not use it, the script may be interrupted by an error resolving a nonexistent or erroneous address.

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

Go to the configuration section / ip firewall address-list and delete all entries in which the list name contains the value of the $ ListName variable. The construction of square brackets allows to execute another within the current command, and transfer the result of the execution as current parameter.

 /ip dns cache all :foreach i in=[find type="A"] do={ 
go to the configuration section / ip dns cahe all. It contains the router's DNS cache in the form of a table Name - Type - Data - TTL. Perform a selection by type - we only need A-records. And we will bypass the result of the selection in the cycle “for everyone”. This will be the main loop of our script.

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

Create variables that are updated in each cycle: two flags - bNew, eliminating duplication, match, indicating whether the current cache entry is in our domain list; The cacheName variable contains the Name field of the current cache entry, that is, the domain.

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

We will go around the list of domains and for each we will check whether the cacheName contains a substring in the form of a domain from this list.
Why not use comparison for equality?
Very simple - the script logic assumes that sub-domains should be processed in the same way as domains. If we want to block social networks, then it makes sense to block not only the main domain, but also, for example, servers giving statics, pictures, scripts, and located on the sub-domains of this site. Also, this will allow you to avoid listing domains separately with “www” and without. The fact that these domains did not fall into the cache when resolving - is not terrible, because they can get there when resolved by the user's browser (although this requires that the user's DNS requests are processed in RouterOS).
If contained, set the value of the match flag to 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 } } } 

In the final stage, if the current address requires adding (match is set to true), then we add it to the address list. The comment to the added record will contain the domain to which it belongs. At the same time we carry out several checks. If the address-list is empty, then we add immediately, if something is there, check if there are already entries with the same IP address, and if not, add it.

The address list needs to be updated periodically. For this, RouterOS has a task manager. The task can be added from the console or from the winbox GUI
 /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 


Scenarios for working with the address list are not limited to creating rules in the firewall. Therefore, I will give a few examples. You can perform in the console, you can add the mouse in winbox'e.
Black list:
 /ip firewall filter add chain=forward protocol=tcp dst-port=80 address-list=DenyThis \ action=drop 

Static route to these nodes
 /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 

Collect customer information
 /ip firewall mangle add action=add-src-to-address-list address-list=FUPer chain=prerouting \ dst-address-list=Pron log=yes log-prefix=critical 


List of sources:
Scripting Documentation
Simple examples from the developers of RouterOS
User-added scripts

UPD: specifically at the request of turone made changes to the script so that the DNS server addresses were taken from the system.
UPD 08/24/2016: I noticed that in new versions of RouterOS (starting from 6.36), it was possible to specify DNS names in the address lists. So now the value of this script is only educational.

Source: https://habr.com/ru/post/242143/


All Articles