📜 ⬆️ ⬇️

Universal script to switch 2 Internet channels Mikrotik

About 2.5 years ago I wrote an article on the topic of automatic switching of the Internet channel to the backup one . The script, of course, still works “perfectly well”, but its appearance and some nuances ...

So, there was a task to improve the script, eliminating the side effects as much as possible. Well, let's get started.

image

At our disposal Mikrotik RB850Gx2 , for which we will write a script (its performance has also been tested on models RB450G and RB951G-2HnD ).
')
First, let's add a new script.
image

Give the script a name, for example, script-check-inet


What will we use in it?

So, first we define the necessary variables:

:local firstInterface "pppoe-rostelecom"; :local secondInterface "eth2-MTS"; :local pingTo1 "8.8.8.8"; :local pingTo2 "217.112.35.75"; :local pingCount 5; :local stableConnectFrom 70; :local prefix ">>> "; :local firstInterfaceName $firstInterface; :local secondInterfaceName $secondInterface; 

Where:
$ firstInterface is the name of our PPPoE primary link connection.
$ secondInterface is the name of our Ethernet connection for the backup line.
$ pingTo1 and $ pingTo2 - IP addresses of resources that we will "kick".
$ pingCount - number of pings for each IP address
$ stableConnectFrom - the percentage to determine the "stability" of the Internet channel. For example, in our case, if over 30% of ping packets are lost (`$ pingStatus <$ stableConnectFrom`, see below), the channel will be switched to the backup one.
$ prefix - we will use to display the logs so that we can see the desired text.

$ firstInterfaceName is a variable in which we “remember” the name of the interface in order to exclude a construct like `[get $ firstInterface gateway]`, which was in the last example

If you change the distance of the route without clearing the ARP table, errors will occur and since there are several places in our code from where you need to call the cleaning of this table, we form the code in the function:

 :local clearArp do={ :local dumplist [/ip arp find] :foreach i in=$dumplist do={ /ip arp remove $i } :log info ($prefix . "ARP cleaned"); } 


Further, to reduce the lines of code, we use the `/ ip route {}` construct, within which other root commands cannot be called, for example, `/ interface pppoe-client ...`. Therefore, we will create another function responsible for reconnecting the PPPoE connection:

 # Function to reconnect PPPoE connection :local reconnectPPPoE do={ /interface pppoe-client set $nameInterface disable=yes; :delay 1s; /interface pppoe-client set $nameInterface disable=no; } 


In our case, Rostelecom is used as the main line, which has a “chip” in the periodic shutdown of the Internet, while their internal line works without failures. This was achieved empirically using the SIP server from Rostelecom, which never fell.
In general, we created the function of reconnecting the connection. Go ahead.

Before the main part, add "protection against fools." Suddenly, our interface is turned off, or the distance is not correct ...

So, we check if the interfaces are active and if not - activate them:

 # Check FIRST interface /interface pppoe-client { :if ( [get $firstInterface disable] = true) do={ set $firstInterface disable=no; :delay 5s; } } # Check SECOND interface /interface ethernet { :if ( [get $secondInterface disable] = true) do={ set $secondInterface disable=no; } } 


But that is not all! Next we check the exposed distance of the routes:

 /ip route { # Set objects to variables :set firstInterface [find dst-address="0.0.0.0/0" gateway=$firstInterfaceName]; :set secondInterface [find dst-address="0.0.0.0/0" gateway=$secondInterfaceName]; # Check routes :if ( [get $firstInterface distance] != 2 ) do={ set $firstInterface distance=2; :log info ($prefix . "Distance for " . $firstInterfaceName . " corrected"); } :if ( [get $secondInterface distance] != 1 && [get $secondInterface distance] != 3) do={ set $secondInterface distance=3; :log info ($prefix . "Distance for " . $secondInterfaceName . " corrected"); } # ... body ... } 


Since the main part works in `/ ip route`, we have added" protection against fools "to its beginning.
In our case, we use 2 routes with `dst. address` equal to `0.0.0.0 / 0`, gateway by interface name and“ some ”distance.

What the script does:

First, it assigns objects to variables for each of the routes in order to write less code.
Then, we check the distance of the main route (Rostelecom). We use the value "2".

After that, we check the distance of the reserve route - it can take the value of the distances either “1” or “3”. This simplifies the task of changing distances, since there is no need to change the distance of the main channel.

Checked the distance and set the right ones. What's next?

Ping check


Since the interface may be inactive, in the original condition we use the construction not only of ping checks, but also of the interface status:

 /ip route { # ... :local pingStatus \ ((( [/ping $pingTo1 interface=$firstInterfaceName count=$pingCount] + \ [/ping $pingTo2 interface=$firstInterfaceName count=$pingCount] ) / ($pingCount * 2)) * 100); :if ( [get $firstInterface active] = false or $pingStatus < $stableConnectFrom) do={ # ... } # ... } 


Since it was decided to enter a percentage to identify the “stability” of the channel, the local variable `$ pingStatus` was added, in which the percentage of successful packages is saved to their total number (thanks to esudnik for the note).

That is, if the interface is turned off or the percentage of successful packages is lower than that specified in the `$ stableConnectFrom` variable, we assume that the Internet is“ dead ”.

Notice the line `[/ ping $ pingTo1 interface = $ firstInterfaceName count = $ pingCount]`, where `interface = ...` is the hard-coded interface through which we will check the ping. Since the main one “falls”, we will indicate its name. This method eliminates the need to add traffic blocking rules for a specific channel with a specific IP address on the firewall tab.

Commentators of the article ( icCE and mafet ) drew attention to the fact that often pings to one resource may disappear, so we introduced 2 resources for "kicks."

  :if ( [get $firstInterface active] = false or $pingStatus < $stableConnectFrom) do={ :log info ($prefix . "FIRST NO INTERNET!!!"); # Change distance :if ( [get $secondInterface distance] != 1 ) do={ set $secondInterface distance=1; :log info ($prefix . "Distance for " . $secondInterfaceName . " changed"); $clearArp; } $reconnectPPPoE nameInterface=$firstInterfaceName; } 


First, we print a log that there is no Internet, after which we change the distance of the backup channel and clean the ARP table by calling the function.
After that, we call the function of reconnecting the PPPoE connection. Since we specify the name of the connection in one place (so as not to multiply the variables), the function is written taking into account the adoption of a variable containing the name of the interface. Thus, when calling a function in the `nameInterface` parameter, we pass the name of the PPPoE interface we need.

The Internet "appeared", or how to get everything back


At this stage, we start the function `if else`:

 /ip route { # ... :if ( [get $firstInterface active] = false or $pingStatus < $stableConnectFrom) do={ # ... } else={ :log info ($prefix . "FIRST INTERNET CONNECTED"); # Change distance :if ( [get $secondInterface distance] != 3 ) do={ set $secondInterface distance=3; :log info ($prefix . "Distance for " . $secondInterfaceName . " changed"); $clearArp; } } # ... } 


As in the first part of the function, we display a network availability message, then change the distance of the backup channel and clean the ARP table.

How to run



Create a record in the scheduler
image

image


In the `Name` field, enter the name of the entry to avoid confusion.
The field `Start Time` I set` 00: 00: 00` to run exactly at midnight.
Interval - 30 seconds
In the field `On Event` enter the name of our script -` script-check-inet`
And click "OK".

That's all!

Below is the full script code below the spoiler.

Full script code
 # Set local variables :local firstInterface "pppoe-rostelecom"; :local secondInterface "eth2-MTS"; :local pingTo1 "8.8.8.8"; :local pingTo2 "217.112.35.75"; :local pingCount 5; :local stableConnectFrom 70; :local prefix ">>> "; # Local variables :local firstInterfaceName $firstInterface; :local secondInterfaceName $secondInterface; # Function to cleaning ARP table :local clearArp do={ :local dumplist [/ip arp find] :foreach i in=$dumplist do={ /ip arp remove $i } :log info ($prefix . "ARP cleaned"); } # Function to reconnect PPPoE connection :local reconnectPPPoE do={ /interface pppoe-client set $nameInterface disable=yes; :delay 1s; /interface pppoe-client set $nameInterface disable=no; } :log info ($prefix . "START PING to $pingTo1 & $pingTo2"); # Check FIRST interface /interface pppoe-client { :if ( [get $firstInterface disable] = true) do={ set $firstInterface disable=no; :delay 5s; } } # Check SECOND interface /interface ethernet { :if ( [get $secondInterface disable] = true) do={ set $secondInterface disable=no; } } /ip route { # Set objects to variables :set firstInterface [find dst-address="0.0.0.0/0" gateway=$firstInterfaceName]; :set secondInterface [find dst-address="0.0.0.0/0" gateway=$secondInterfaceName]; # Check routes :if ( [get $firstInterface distance] != 2 ) do={ set $firstInterface distance=2; :log info ($prefix . "Distance for " . $firstInterfaceName . " corrected"); } :if ( [get $secondInterface distance] != 1 && [get $secondInterface distance] != 3) do={ set $secondInterface distance=3; :log info ($prefix . "Distance for " . $secondInterfaceName . " corrected"); } # Get ping successfully packets. In percent :local pingStatus \ ((( [/ping $pingTo1 interface=$firstInterfaceName count=$pingCount] + \ [/ping $pingTo2 interface=$firstInterfaceName count=$pingCount] ) / ($pingCount * 2)) * 100); # Check Internet :if ( [get $firstInterface active] = false or $pingStatus < $stableConnectFrom) do={ :log info ($prefix . "FIRST NO INTERNET!!!"); # Change distance :if ( [get $secondInterface distance] != 1 ) do={ set $secondInterface distance=1; :log info ($prefix . "Distance for " . $secondInterfaceName . " changed"); $clearArp; } $reconnectPPPoE nameInterface=$firstInterfaceName; } else={ :log info ($prefix . "FIRST INTERNET CONNECTED"); # Change distance :if ( [get $secondInterface distance] != 3 ) do={ set $secondInterface distance=3; :log info ($prefix . "Distance for " . $secondInterfaceName . " changed"); $clearArp; } } } :log info ($prefix . "END PING"); 



UPD


Initially, the script was written and tested on Mikrotik RB450G and RB951G-2HnD devices (`mipsbe` kernel), but when launched on the RB850Gx2 model (` powerpc` kernel), an error occurred in the line: `[/ ping $ pingTo interface = [get $ firstInterface gateway] count = 5] `, namely, the failure of` [get $ firstInterface gateway] `processing is inside` [/ ping ...] `.

Since we have introduced the additional variable `$ firstInterfaceName`, we managed to change this construction and achieved the efficiency of the script on all the listed devices.

UPD 2


The script code has been updated once again. esudnik drew attention to the problem of a large number of lost packets while actively connected to the network. Thus, we have introduced an additional variable (`stableConnectFrom`), used to determine the percentage of the" quality "of the line.

In the example, the value of the variable is equal to "70". This means that if the percentage of successfully sent packets is below 70%, the script activates the backup channel.

UPD 3


As noted icCE , when using the script on firmware 6.36rc10, an error occurs:

  :local pingStatus \ (( [/ping $pingTo1 interface=$firstInterfaceName count=$pingCount] + \ [/ping $pingTo2 interface=$firstInterfaceName count=$pingCount] ) / ($pingCount * 2)) * 100; 


The firmware does not like " * 100 ". The solution to the problem was simple - to frame the calculated values ​​in additional brackets, getting

  :local pingStatus \ ((( [/ping $pingTo1 interface=$firstInterfaceName count=$pingCount] + \ [/ping $pingTo2 interface=$firstInterfaceName count=$pingCount] ) / ($pingCount * 2)) * 100); 


The text of the script in the article has been updated.

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


All Articles