📜 ⬆️ ⬇️

The study of variables Mikrotik. Script update Dynamic DNS records FreeDNS.afraid.org

I use Mikrotik as a home and office router, and in general I like the system very much. RouterOS has extensive capabilities that cover 90% of my tasks, if something is missing, then you can add functionality using internal scripts. But when you start writing a more or less sane script, or you try to understand and apply someone else's recipe, the outlines of the underwater part of the iceberg become noticeable, strange features of the language come up.

I did a little research on the variables in the Mikrotik scripts, reviewed the declaration and initialization under a magnifying glass.
It turned out, in my opinion, a worthy topic for writing an article. So let's get started.

What does Manual: Scripting tell us about variables in scripts? And he tells us that variables come in two scopes: local and global, that they are declared by commands -

: local <name> <value>
and
: global <name> <value>
')
I propose to immediately focus on global variables, since they are easier to explore due to their better “observability”, and most of the conclusions, I think, can be easily transferred to local.

Before we declare the first global variable, let's take a look at the features of some built-in data types and character constructs that can be used as variable values ​​or arguments of comparison operators.

Manual: Scripting tells us that there are about a dozen types, of which we consider only a few that are important for understanding variables. So, explicit and logical types: numeric num , string str, and array . Further the type is mentioned (it is the value of the variable) nil , about which it is written that the variable will have it by default if nothing is assigned to it. Believe it.

If you work with the command console in WinBox, you will notice that there is another strange keyword, nothing , which is unclear what some “nothing” means.

[admin@MikroTik] > :global var0 [admin@MikroTik] > :put [:typeof $var0] nothing [admin@MikroTik] > /environment print var0=[:nothing] 

Well, where is nil ? But full of nothing .

Study nil


In general, to deal with these special types and values, we apply a systematic approach, analyze the issuance of the same type of requests, but with different sets of source data. And at the beginning we will have different “strange” constants and expressions as input data. The result is the following table:
NoWhat is it?<value><value2>: if (<value> = <value2>) do = {: put TRUE} else = {: put FALSE}: put [: typeof <value>]: put [: typeof [ <value> ] ]: put [: len <value>]: put [: len [ <value> ] ]
oneNothing in command wrapper[] / [: nothing] / or even so [:][]TRUEnilnil00
2Empty line"""" / {}TRUEstrexpected command name00
3Nothing inside an expression(: nothing) / or even like this (:)[] / "" / {}FALSEnothingnil00
fourArray of nothing{: nothing} / {:}{} / "TRUEarrayniloneone
fiveArray element nothing({: nothing} -> 0)[] / "" / {}FALSEnothingnil00

Here are the results of comparing the various constants of expressions and commands over the constants among themselves. Understanding the properties of constants will help when writing the right-hand parts of comparison operators.

At the expense of the table fields. In the Mikrotik script language, square brackets denote embedding the result of a command into a general expression, therefore in the general sense of value! = [Value], this is important.

I will comment line by line:

Line 1: all options for writing the value field are synonymous! In the future, I suggest using concise [] . I repeat that square brackets denote embedding the result of a command into a general expression. As you can see, this is one of the ways to “generate” nil not in its pure form, but as the result of an empty command in square brackets, which is exactly equal to nil .

Line 2:
It's simple. An empty string is an obvious thing. The only moment is that it turns out to be an array of nothing, but I suggest not paying special attention to it. And you can not use an empty string as a command in square brackets, this is the only place where the terminal has not swallowed the syntax on the source data of the table, it is logical.

Line 3:
Parentheses carry an expression, inside you can also put nothing , and the result of such an expression will also be nothing , in fact it is almost pure nothing . It can be seen that the type of the result of the expression (: nothing) gives nothing , and the type from the wrapper from the command brackets [] gives nil , by analogy with line 1. In general, after reconsidering what was written, I realized that this is speculation, since anything can be put inside () , any meaningless set of characters, the main thing is that the result (...) gives nothing .

Line 4:
An array that contains nothing actually contains 1 element. The type, as expected, array , the result type of the command wrapper also gives nil

Line 5:
We get to the array element from nothing, there is a complete analogy of output with line 3. In general, such an element is also a pure nothing .

What general conclusions can be drawn from this strange brainfuck. nothing really exists, they can be operated on, rare expressions can return it, but the commands in [] never return nothing , only nil . Another more important conclusion is that the operator of the length of a value or number of array elements : len behaves very stable and generates a predictable result, so I can definitely recommend it for use in scripts when you need to check the values ​​returned by expressions and commands. And that [] = [: nothing] = nil .

The table gives an idea of ​​what different commands and expressions of the Mikrotik language can return.

Study variable declarations


We now turn to a more practical declaration of variables.
NoWhat is it?Announcement<val><val2>: put <val>: if (<val> = <val2>) do = {: put TRUE} else = {: put FALSE}: put [: typeof <val>]: put [: typeof [ <val> ] ]: put [: len <val>] is the same: put [: len [ <val> ] ]/ environment print
oneNo assignment: global var1$ var1[] / (:)FALSE / TRUEnothingnil0var1 = [: nothing]
2Assignment deleting variable: global var2 (: nothing)$ var2(:)TRUEnothingnil0-
3Nil assignment: global var3 []$ var3[]TRUEnilnil0var3 = []
fourAssigning an empty string: global var4 ""$ var4"" / {}TRUE / TRUEstrstr0var4 = ""
5 (3)Strange nil , analog 3: global var5 [{}]$ var5[{}] / []TRUE / TRUEnilnil0var5 = []
6Array of nothing: global var6 {:}$ var6"" / {""} / {}TRUE / TRUE / TRUEarrayarrayonevar6 = {[: nothing]}
7 (6, 8)Empty string array: global var7 {""}$ var7"" / {""} / {}TRUE / TRUE / TRUEarrayarrayonevar7 = {""}
8 (6, 7)Nil array: global var8 {[]}$ var8"" / {""} / {}TRUE / TRUE / TRUEarrayarrayonevar8 = {[]}
NoWhat is it?Announcement<val><val2>: put <val>: if (<val> = <val2>) do = {: put TRUE} else = {: put FALSE}: put [: typeof <val>]: put [: typeof [ <val> ] ]: put [: len <val>] is the same: put [: len [ <val> ] ]/ environment print
9Assignment number: global var9 123$ var9n / a123n. / p.numnum3var9 = 123
tenAssign string: global var10 "987"$ var10n / a987n. / p.strstr3var10 = "987"
elevenArray of one number: global var11 {555} /: global var11 {555;}$ var11n / a555n. / p.arrayarrayonevar11 = {555}
12Array of disparate elements: global var12 {33; "test123"}$ var12n / a33; test123n. / p.arrayarray2var12 = {33; "test123"}
13Array element- // -($ var12-> 0)n / a33n. / p.numnum2- // -
14Array element- // -($ var12-> 1)n / atest123n. / p.strstr7- // -
15Array with nothing element: global var13 {33; (:)}$ var13n / a33;n. / p.arrayarray2var13 = {33; [: nothing]}
sixteenArray element- // -($ var13-> 0)n / a33n. / p.numnum2- // -
17Array element- // -($ var13-> 1)n / an. / p.nothingnil0- // -
18Array with nil element: global var14 {1012; []}$ var14n / a1012;n. / p.arrayarray2var14 = {1012; []}
nineteenArray element- // -($ var14-> 1)n / an. / p.nilnil0- // -

n / a - not used; n. / p. - not applicable

Line comments to the table:

1. The variable is created, but nothing is assigned to it. The variable contains nothing .
2. If we assign such an expression to a variable, this will lead to the removal of the global variable from the environment variables.
3. The standard way to create empty variables. The variable contains nil .
4. Assigning an empty string. It's all obvious.
5. It turns out that such an expression is similar to [] simple assignment of nil , as in 3. I think this is because inside [] a non-existent command and the result of this command gives nil
6, 7, 8. Assigning curly brackets makes an array of variables, albeit empty. Note that the records have the same results in the table, but this does not apply to the properties of the elements of these arrays. Properties of array elements are discussed below.
9, 10. Simple data types. It's pretty obvious.
11. Array of one element, note that {555;} results equals {555}
12, 13, 14. The array may include elements of different data types. The study of the properties of the elements of the array gives predictable results.
15, 16, 17. One of the elements of the nothing array. The elements of the array have the same properties as the simple variables and constants of these types and values. There is an analogy with paragraph 2.
18, 19. An analogy with paragraph 3.

The study, in my opinion, turned out a little controversial, but I really hope it will bring more order to your understanding of Mikrotik than chaos. As an additional compensation, I publish a script for working with the dynamic DNS of the wonderful service FreeDNS.afraid.org.

Script for FreeDNS.afraid.org


I saw several similar scripts, but I didn’t like them due to different restrictions, so I decided to assemble my own bike, which would completely suit me.

I took the script from LESHIYODESSA as a basis . I didn’t like his algorithm, which used the file to store the current addresses of Dynamic DNS records and periodically parsed it, besides, the script does not support updating different records, it is suggested to duplicate the script for this, but this does not remove the problem of updating the record by the specified IP address Therefore, in fact, I wrote my own script in which I replaced the work with files with a more reliable periodic update mechanism (with an hourly interval) and a forced update to change the monitored IP addresses of interfaces obtained via DHCP, independent of several entries.

We declare arrays of the names of the subdomains of FreeDNS.afraid.org and their hashes, the names of WAN interfaces, with which we will be tracking IP addresses. And also we set the number of records (Quant) by the size of the array either manually:

 :local SubdomainHashes {"U3dWVE5V01TWxPcjluEo0bEtJQWjg5DUz=";"U3pWV5VFTWxPcjlOEo0EtJpOE1MAyDc="} :global DNSDomains {"aaa.xyz.pu";"bbb.xyz.pu"} :global WANInterfaces {"ether4-WAN-Inet";"ether3-WAN-Beeline"} :global Quant [:len $DNSDomains] 

We declare auxiliary variables:

SkipCounters - an array of checks of monitored interfaces
LastIPs is an array of IP addresses that have already been sent to FreeDNS.

An array of counters will allow you to update Dynamic DNS records independently of each other.

First, I make an empty variable declaration : global SkipCounters , such a declaration allows you to either create a new global variable, or use the environment that already exists in the environment variables and its value without overwriting it.

The following constructions work for the newly created variables, check the data type, if it is not an array, then the type changes to an array, and the values ​​of the variables are assigned. Thus, at the output, we have array variables initialized by necessary values.

 :global SkipCounters :if ([:typeof $SkipCounters] != "array") do={ :set SkipCounters {""} :for i from=0 to=($Quant-1) do={:set ($SkipCounters->$i) 1} } :global LastIPs :if ([:typeof $LastIPs] != "array") do={ :set LastIPs {""} :for i from=0 to=($Quant-1) do={:set ($LastIPs->$i) ""} } 

Nor the actual tracking-update algorithm itself.

We get the current IP address from dhcp-client. Next is the most interesting.

The [/ ip dhcp-client get [find where interface = ($ WANInterfaces -> $ i)] address] command can generally return anything other than the line containing the IP address, so it will normally update the value of the CurrentIP variable. The return value can be either a string with IP, or nil, or there will be a runtime error and the command will not update CurrentIP. Therefore, I enter the explicit declaration in the line above : local CurrentIP "" . And after executing the command in CurrentIP, it will be either "", or nil, or an IP address.

As I wrote above, the operator: len has the greatest stability, so we use it further to check the adequacy of the data [: len $ CurrentIP]> 0 . We also track the value of the counter, and if it is> = 60, we forcefully send the request to FreeDNS. This increases the stability of the algorithm to communication problems. I run the script in the scheduler once a minute, so the mandatory update period is about 1 hour, which does not burden the FreeDNS service.

What else you should pay attention. The URL of the update request contains the parameter "& address =". $ CurrentIP, this parameter allows you to explicitly specify the IP address for the subdomain instead of the automatic one (on the interface from which the request left).

 :for i from=0 to=($Quant-1) do={ :local CurrentIP "" :set CurrentIP [/ip dhcp-client get [find where interface=($WANInterfaces->$i)] address] :set CurrentIP [:pick $CurrentIP 0 ([:len $CurrentIP]-3)] # :log info ("Current SkipCounter$i: ".($SkipCounters->$i)) :if ([:len $CurrentIP] > 0 and ($CurrentIP != ($LastIPs->$i) or ($SkipCounters->$i) > 59)) do={ :if ($CurrentIP != ($LastIPs->$i)) do={ :log info ("Service Dynamic DNS: Renew IP: ".($LastIPs->$i)." for ".($DNSDomains->$i)." to $CurrentIP") } /tool fetch url=("http://freedns.afraid.org/dynamic/update.php\?".($SubdomainHashes->$i)."&address=".$CurrentIP) keep-result=no :set ($LastIPs->$i) $CurrentIP :set ($SkipCounters->$i) 1 } else={ :set ($SkipCounters->$i) (($SkipCounters->$i) + 1) } } 

The entire MultiFreeDNS script
 # MultiFreeDNS :local SubdomainHashes {"U3dWVE5V01TWxPcjluEo0bEtJQWjg5DUz=";"U3pWV5VFTWxPcjlOEo0EtJpOE1MAyDc="} :global DNSDomains {"aaa.xyz.pu";"bbb.xyz.pu"} :global WANInterfaces {"ether4-WAN-Inet";"ether3-WAN-Beeline"} :global Quant [:len $DNSDomains] :global SkipCounters :if ([:typeof $SkipCounters] != "array") do={ :set SkipCounters {""} :for i from=0 to=($Quant-1) do={:set ($SkipCounters->$i) 1} } :global LastIPs :if ([:typeof $LastIPs] != "array") do={ :set LastIPs {""} :for i from=0 to=($Quant-1) do={:set ($LastIPs->$i) ""} } :for i from=0 to=($Quant-1) do={ :local CurrentIP "" :set CurrentIP [/ip dhcp-client get [find where interface=($WANInterfaces->$i)] address] :set CurrentIP [:pick $CurrentIP 0 ([:len $CurrentIP]-3)] # :log info ("Current SkipCounter$i: ".($SkipCounters->$i)) :if ([:len $CurrentIP] > 0 and ($CurrentIP != ($LastIPs->$i) or ($SkipCounters->$i) > 59)) do={ :if ($CurrentIP != ($LastIPs->$i)) do={ :log info ("Service Dynamic DNS: Renew IP: ".($LastIPs->$i)." for ".($DNSDomains->$i)." to $CurrentIP") } /tool fetch url=("http://freedns.afraid.org/dynamic/update.php\?".($SubdomainHashes->$i)."&address=".$CurrentIP) keep-result=no :set ($LastIPs->$i) $CurrentIP :set ($SkipCounters->$i) 1 } else={ :set ($SkipCounters->$i) (($SkipCounters->$i) + 1) } } 

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


All Articles