Community discussions

MikroTik App
 
Halesk2k
just joined
Topic Author
Posts: 20
Joined: Sat Sep 08, 2018 4:13 pm

Failover script with 2 dhcp-client interfaces (sharing my working script)

Fri Aug 27, 2021 2:21 am

Hello,

Here is my script to failover between 2 links set up as dhcp-client.

latest release: https://github.com/loiklo/mikrotik/blob ... lover-dhcp

Features:
- main/backup failover with 2 dhcp-client interfaces
- rely on multiple remote test hosts to decide if the main link is working properly
- automaticaly add/remove required routes to remote test hosts
- adjust testHosts routes if the gateway change
- configurable "ping success rate expected"
- stop pinging testHosts as soon as the expected rate is reached to avoid unnecessary traffic
- release DHCP lease in mainInterface on link failure to trigger a renew.
(This is usefull if the provider change the subnet on its side but our DHCP lease is not expired yet)
- doesn't need :global variable
- doesn't need specific configuration other than using dhcp-client on both main/backup interfaces

Limitations:
- hosts in testHosts array can't be reach through backupInterface if mainInterface have an active DHCP lease
- mainInterface and backupInterface must be configured in dhcp-client
- Only work with IPv4
{
  # Mikrotik failover script with dual dhcp interfaces

  # Copyright (C) 2021 Alexandre PIERRET
  #
  # This program is free software: you can redistribute it and/or modify it under
  # the terms of the GNU General Public License as published by the Free Software
  # Foundation, either version 3 of the License, or (at your option) any later
  # version.
  #
  # This program is distributed in the hope that it will be useful, but WITHOUT
  # ANY WARRANTY; without even the implied warranty of  MERCHANTABILITY or FITNESS
  # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  #
  # You should have received a copy of the GNU General Public License along with
  # this program.  If not, see <http://www.gnu.org/licenses/>.

  # Changelog:
  # - 2021/08/27: Initial release

  # Features:
  # - main/backup failover with 2 dhcp-client interfaces
  # - rely on multiple remote test hosts to decide if the main link is working properly
  # - automaticaly add/remove required routes to remote test hosts
  # - adjust testHosts routes if the gateway change
  # - configurable "ping success rate expected"
  # - stop pinging testHosts as soon as the expected rate is reached to avoid unnecessary traffic
  # - release DHCP lease in mainInterface on link failure to trigger a renew.
  #   (This is usefull if the provider change the subnet on its side but or DHCP lease is not expired yet)
  # - doesn't need :global variable
  # - doesn't need specific configuration other than using dhcp-client on both main/backup interfaces
  
  # Limitations:
  # - hosts in testHosts array can't be reach through backupInterface if mainInterface receive a DHCP lease
  # - mainInterface and backupInterface must be configured in dhcp-client
  # - Only work with IPv4
  
  # How to use it
  # - Set the mainInterface and the backupInterface variable (dhcp client configuration must exist)
  # - Set the testHosts you want to use as reliable remote host to test connectivity
  # - Upload the script to your router
  # - Schedule it to run every minutes

  # Original testHosts list:
  # - 4.2.2.1        => CenturyLink DNS server
  # - 192.175.48.1   => AS112 - blackhole DNS for RFC1918
  # - 1.0.0.1        => Cloudflare DNS server
  # - 137.138.44.217 => CERN looking glass
  # - 8.8.4.4        => Google DNS server #2

  ################################
  ### User variables
  ################################
  # Basic
  :local mainInterface "ether1"; # My FTTH ONU (dhcp)
  :local backupInterface "ether2"; # My LTE modem (dhcp)
  :local testHosts {
    "4.2.2.1";
    "192.175.48.1";
    "1.0.0.1";
    "137.138.44.217";
    "8.8.4.4";
  }
  :local pingPerHost 2; # How many ping per host
  :local pingRateSucessExpected 5; # Success rate expected (in percent)

  # Expert
  :local pingSuccessDelay 100ms; # ping success throttle
  :local mainPromoteRouteDistance 10; # Route distance when mainInterface is promote
  :local backupRouteDistance 20; # Backup interface route distance
  :local mainDemoteRouteDistance 30; # Route distance when mainInterface is demote
  :local routeComment "managed-by-failover-script"; # Comment link to routes added to force testHosts through mainInterface

  ################################
  ### Code
  ################################
  # Remove previously added routes that are no longer in testHosts (based on route comment)
  :foreach managedRoute in=[/ip route find where comment=$routeComment] do={
    :local lPrefix [/ip route get $managedRoute dst-address]
    :local lHost [:pick $lPrefix 0 [:find $lPrefix "/"]]; # Remove ending /32
    # if $lHost not in $testHosts
    :if ([:typeof [:find $testHosts $lHost]]="nil") do={
      /ip route remove $managedRoute;
    }
  }

  # Intenal variables
  :local currentMainRouteDistance [/ip dhcp-client get [/ip dhcp-client find where interface=$mainInterface] default-route-distance];
  :local currentBackupRouteDistance [/ip dhcp-client get [/ip dhcp-client find where interface=$backupInterface] default-route-distance];

  # If required, set the backup default route distance
  :if ($currentBackupRouteDistance != $backupRouteDistance) do={
    /ip dhcp-client set [/ip dhcp-client find where interface=$backupInterface] default-route-distance=$backupRouteDistance;
  }

  # Add/remove more-specific routes to testHosts depending of mainInterface dhcp status and lease
  # If DHCP status is bound:
  #   Remove testHosts routes from previous lease (if any)
  #   Add testHosts /32 routes if not already exist
  # Else (DHCP status not bound):
  #   Remove all testHosts /32 routes
  :local mainGatewayDhcpInfo [:pick [/ip dhcp-client print as-value detail where interface=$mainInterface] 0];
  :local mainGatewayDhcpStatus ($mainGatewayDhcpInfo->"status");
  :local mainGatewayDhcpGatewayIp ($mainGatewayDhcpInfo->"gateway");
  :if ($mainGatewayDhcpStatus = "bound") do={
    :foreach testHost in=$testHosts do={
      :local testHostIp [:tostr ("$testHost"."/32")]; # Generate x.x.x.x/32 str from x.x.x.x
      # Remove route from previous lease (if any)
      /ip route remove [/ip route find where dst-address=$testHostIp and gateway!=[:tostr $mainGatewayDhcpGatewayIp]];
      # Add route if not already exists
      :if ([:len [/ip route find dst-address=$testHostIp]] = 0) do={
        /ip route add dst-address=$testHostIp gateway=[:tostr $mainGatewayDhcpGatewayIp] comment=$routeComment;
      }
    }
  } else={
    # Remove all testHosts /32 routes
    :foreach testHost in=$testHosts do={
      :local testHostIp [:tostr ("$testHost"."/32")];
      /ip route remove [/ip route find where dst-address=[:tostr $testHostIp]];
    }
  }

  # Ping loop, can exit earlier if the sucess rate is reached
  :local pingSuccessCount 0;
  :local pingCount 1;
  :local pingRateSucess 0;
  :while (($pingCount <= $pingPerHost) and ($pingRateSucess < $pingRateSucessExpected)) do={
    :foreach tHost in=$testHosts do={
      :if ([/ping address=$tHost count=1]=1) do={
        :set pingSuccessCount ($pingSuccessCount + 1);
        :delay [:totime $pingSuccessDelay];
      }
    }
    :set pingCount ($pingCount + 1);
    :set pingRateSucess ($pingSuccessCount * 100 / ([:len $testHosts] * $pingPerHost));
  }

  
  :if ($pingRateSucess < $pingRateSucessExpected) do={
    # Demote mainInterface
    :if ($currentMainRouteDistance != $mainDemoteRouteDistance) do={
      /ip dhcp-client set [/ip dhcp-client find where interface=$mainInterface] default-route-distance=$mainDemoteRouteDistance;
      :log info "Detected main link failure, switching to backup link"
      #>>> ADD HERE ACTIONS TO DO ON MAIN LINK FAILURE (email, http call, logging, ...)
    }
    # Release DHCP lease if bound to:
    # - allow the testHosts to be reached through the backupInterface
    # - force a DHCP renew in case of mainInterface subnet has changed on the provider side but our lease is not expired yet
    :if ($mainGatewayDhcpStatus = "bound") do={
      /ip dhcp-client release [/ip dhcp-client find interface=$mainInterface];
    }
  } else={
    # Promote mainInterface
    :if ($currentMainRouteDistance != $mainPromoteRouteDistance) do={
      /ip dhcp-client set [/ip dhcp-client find where interface=$mainInterface] default-route-distance=$mainPromoteRouteDistance;
      :log info "Main link back online, switching back to main link"
      #>>> ADD HERE ACTIONS TO DO ON MAIN LINK BACK ONLINE (email, http call, logging, ...)
    }
  }
}
 
whaller
just joined
Posts: 2
Joined: Fri Jul 09, 2021 7:23 am

Re: Failover script with 2 dhcp-client interfaces (sharing my working script)

Thu Jun 02, 2022 10:36 pm

I've reviewed your script and have tried to get it to work with no luck. I've uploaded it and tried to run it via the scheduler as well as created the script within Winbox and scheduled it, both with no luck. I've updated the following variables:
 
whaller
just joined
Posts: 2
Joined: Fri Jul 09, 2021 7:23 am

Re: Failover script with 2 dhcp-client interfaces (sharing my working script)

Thu Jun 02, 2022 10:37 pm

sorry, didn't mean to post the previous note without the following:
:local mainInterface "ether1"; # ubiquiti AP w/ (dhcp)
:local backupInterface "wlan1"; # Mikrotik Wifi AP Station (dhcp)

Who is online

Users browsing this forum: mbezuidenhout, UkRainUa and 19 guests