Universal script for switching 2 Mikrotik Internet channels
- Tutorial
About 2.5 years ago I wrote an article on the topic of automatically switching the Internet channel to the backup one . The script, of course, to this day works "perfectly", but its appearance and some nuances ...
So, the task was to improve the script, eliminating side effects as much as possible. Well, let's get started.

We have at our disposal Mikrotik RB850Gx2 , for which we will write a script (its performance has also been tested on the RB450G and RB951G-2HnD models ).
What will we use in it?
So, for starters, let's define the necessary variables:
where:
$ firstInterface is the name of our main line PPPoE connection.
$ secondInterface is the name of our redundant Ethernet connection.
$ pingTo1 and $ pingTo2 are the IP addresses of the resources we will “kick”.
$ pingCount - the number of pings per IP address
$ stableConnectFrom - percentage for determining the "stability" of the Internet channel. For example, in our case, if more than 30% of ping packets are lost (`$ pingStatus <$ stableConnectFrom`, more on that below), the channel will be switched to the backup one.
$ prefix - we will use it to display logs so that we can see the desired text.
$ firstInterfaceName- a variable into which we “remember” the name of the interface in order to exclude the design of the form “[get $ firstInterface gateway]”, which was in the previous example
When changing the route distance without clearing the ARP table, errors will occur and since there are several places in our code, where it is necessary to call the cleaning of this table, we will issue the code into a function:
Further, to reduce the lines of code, we use the `/ ip route {}` construct, inside which you cannot call other root commands, for example, `/ interface pppoe-client ...`. Therefore, we will create another function responsible for reconnecting the PPPoE connection:
In our case, Rostelecom is used as the main line, which has a “trick” in periodically shutting down the Internet despite the fact that their internal line works without failures. This was achieved experimentally, using the SIP server from Rostelecom, which never fell down.
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:
But that is not all! Next we will check the set route distances:
Since the main part works in `/ ip route`, we added" protection against fools "to its beginning.
In our case, we use 2 routes with `dst. address` equal to `0.0.0.0 / 0`, a gateway by the name of the interface and" some "distance.
What the script does:
First, it assigns objects to the 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 backup 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?
Since the interface may be inactive, in the initial condition we use the construction of not only ping verification, but also the interface status:
Since it was decided to enter a percentage to identify the "stability" of the channel, the local variable `$ pingStatus` was added, which saves the percentage of successful packets to the total number of them (thanks esudnik for the note).
That is, if the interface is turned off or the percentage of successful packets is lower than what we specified in the `$ stableConnectFrom` variable, we consider that the Internet is“ dry ”.
Pay attention to the line `[/ ping $ pingTo1 interface = $ firstInterfaceName count = $ pingCount]`, where `interface = ...` is the hard-coded interface through which we will check 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 on one resource can disappear, so we introduced 2 resources for “kicks”.
First, display the log that there is no Internet, then 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 produce variables), the function is written taking into account the adoption of a variable containing the name of the interface. Thus, when calling the function in the `nameInterface` parameter, we pass the name of the PPPoE interface we need.
At this point, we invoke the `if else` function:
As in the first part of the function, we display a message about network availability, after which we change the distance of the backup channel and clean the ARP table.
In the `Name` field, enter the name of the entry so as not to get confused.
Field `Start Time` I set` 00: 00: 00` to start at exactly midnight.
Interval - 30 seconds
In the field `On Event` we enter the name of our script -` script-check-inet`
And click "OK".
That, in fact, is all!
Below the spoiler is the full script code.
The script was originally written and tested on Mikrotik RB450G and RB951G-2HnD devices (the `mipsbe` kernel), but when starting on the RB850Gx2 model ( the` powerpc` kernel), an error occurred in the line: `[/ ping $ pingTo interface = [get $ firstInterface gateway] count = 5] `, namely in the processing failure of` [get $ firstInterface gateway] `inside` [/ ping ...] `.
Since we introduced the additional variable `$ firstInterfaceName`, we managed to change this design and achieved the script working on all the listed devices.
The script code has once again been updated. esudnik drew attention to the problem of a large number of lost packets with an active network connection. Thus, we 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.
As icCE noted , when using the script on firmware 6.36rc10, an error occurs:
The firmware does not like " * 100 ". The solution to the problem was simple - to frame the calculated values in additional brackets, receiving
The script text in the article was updated.
So, the task was to improve the script, eliminating side effects as much as possible. Well, let's get started.

We have at our disposal Mikrotik RB850Gx2 , for which we will write a script (its performance has also been tested on the RB450G and RB951G-2HnD models ).
First, add a new script
Name the script, for example, script-check-inet

Name the script, for example, script-check-inet
What will we use in it?
So, for starters, let's 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 main line PPPoE connection.
$ secondInterface is the name of our redundant Ethernet connection.
$ pingTo1 and $ pingTo2 are the IP addresses of the resources we will “kick”.
$ pingCount - the number of pings per IP address
$ stableConnectFrom - percentage for determining the "stability" of the Internet channel. For example, in our case, if more than 30% of ping packets are lost (`$ pingStatus <$ stableConnectFrom`, more on that below), the channel will be switched to the backup one.
$ prefix - we will use it to display logs so that we can see the desired text.
$ firstInterfaceName- a variable into which we “remember” the name of the interface in order to exclude the design of the form “[get $ firstInterface gateway]”, which was in the previous example
When changing the route distance without clearing the ARP table, errors will occur and since there are several places in our code, where it is necessary to call the cleaning of this table, we will issue the code into a function:
:local clearArp do={
:local dumplist [/ip arp find]
:foreach i in=$dumplistdo={
/ip arp remove $i
}
:log info ($prefix . "ARP cleaned");
}
Further, to reduce the lines of code, we use the `/ ip route {}` construct, inside which you cannot call other root commands, 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$nameInterfacedisable=yes;
:delay 1s;
/interface pppoe-client set$nameInterfacedisable=no;
}
In our case, Rostelecom is used as the main line, which has a “trick” in periodically shutting down the Internet despite the fact that their internal line works without failures. This was achieved experimentally, using the SIP server from Rostelecom, which never fell down.
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 $firstInterfacedisable] = true) do={
set$firstInterfacedisable=no;
:delay 5s;
}
}
# Check SECOND interface
/interface ethernet {
:if ( [get $secondInterfacedisable] = true) do={
set$secondInterfacedisable=no;
}
}
But that is not all! Next we will check the set route distances:
/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 added" protection against fools "to its beginning.
In our case, we use 2 routes with `dst. address` equal to `0.0.0.0 / 0`, a gateway by the name of the interface and" some "distance.
What the script does:
First, it assigns objects to the 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 backup 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 initial condition we use the construction of not only ping verification, but also 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, which saves the percentage of successful packets to the total number of them (thanks esudnik for the note).
That is, if the interface is turned off or the percentage of successful packets is lower than what we specified in the `$ stableConnectFrom` variable, we consider that the Internet is“ dry ”.
Pay attention to the line `[/ ping $ pingTo1 interface = $ firstInterfaceName count = $ pingCount]`, where `interface = ...` is the hard-coded interface through which we will check 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 on one resource can 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, display the log that there is no Internet, then 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 produce variables), the function is written taking into account the adoption of a variable containing the name of the interface. Thus, when calling the function in the `nameInterface` parameter, we pass the name of the PPPoE interface we need.
Internet “appeared”, or how to get everything back
At this point, we invoke the `if else` function:
/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 message about network availability, after which we change the distance of the backup channel and clean the ARP table.
How to run
Create a record in the sheduler



In the `Name` field, enter the name of the entry so as not to get confused.
Field `Start Time` I set` 00: 00: 00` to start at exactly midnight.
Interval - 30 seconds
In the field `On Event` we enter the name of our script -` script-check-inet`
And click "OK".
That, in fact, is all!
Below the spoiler is the full script code.
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=$dumplistdo={
/ip arp remove $i
}
:log info ($prefix . "ARP cleaned");
}
# Function to reconnect PPPoE connection
:local reconnectPPPoE do={
/interface pppoe-client set$nameInterfacedisable=yes;
:delay 1s;
/interface pppoe-client set$nameInterfacedisable=no;
}
:log info ($prefix . "START PING to $pingTo1 & $pingTo2");
# Check FIRST interface
/interface pppoe-client {
:if ( [get $firstInterfacedisable] = true) do={
set$firstInterfacedisable=no;
:delay 5s;
}
}
# Check SECOND interface
/interface ethernet {
:if ( [get $secondInterfacedisable] = true) do={
set$secondInterfacedisable=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
The script was originally written and tested on Mikrotik RB450G and RB951G-2HnD devices (the `mipsbe` kernel), but when starting on the RB850Gx2 model ( the` powerpc` kernel), an error occurred in the line: `[/ ping $ pingTo interface = [get $ firstInterface gateway] count = 5] `, namely in the processing failure of` [get $ firstInterface gateway] `inside` [/ ping ...] `.
Since we introduced the additional variable `$ firstInterfaceName`, we managed to change this design and achieved the script working on all the listed devices.
UPD 2
The script code has once again been updated. esudnik drew attention to the problem of a large number of lost packets with an active network connection. Thus, we 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 icCE noted , 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, receiving
:local pingStatus \
((( [/ping $pingTo1 interface=$firstInterfaceName count=$pingCount] + \
[/ping $pingTo2 interface=$firstInterfaceName count=$pingCount] ) / ($pingCount * 2)) * 100);
The script text in the article was updated.