Fallback PPPoE credentials on RouterOS

Submitted by davidc on Tue, 06/26/2018 - 22:11

RouterOS has no native concept of fallback PPPoE credentials. You can only enter one username and password per interface. Sometimes you would like to enter a fallback set that are tried if the primary credentials don't work, for example to prepare for a migration. Here is a RouterOS script to handle fallback PPPoE credentials that will be tried one after another until one set succeeds.

Use case example

Your customer is migrating to another ISP, you know what the new credentials will be (but they won't work in advance of the migration), and you don't want to have to do a site visit just to enter new credentials.

How it works

First it checks if the specified PPPoE interface is up. If it is up (or if it's down because it's disabled), it doesn't need to do anything.

When the interface is down, it looks at what credentials are currently in use, finds the next set in the array (wrapping back to the start if necessary) and changes the PPPoE interface to use these instead.

So it loops through all the supplied credentials until the interface (hopefully) comes back up.

How to use

  1. Add the script to System->Scripts with a name like cycle-pppoe-credentials.
  2. You need to configure two things at the top of the script:
    • pppoeInterface is the PPPoE interface name to work on
    • pppoeCredentials is the list of credentials (username and password pairs) to try. You must add the current credentials here [see footnote for why] or the script will not operate.
  3. Schedule it at System->Scheduler, have a schedule that just executes /system script run cycle-pppoe-credentials. Run every X minutes by setting the interval to 00:0X:00. (I suggest that X is not set too low, as some DSLAMs start performing exponential backoff and refuse to respond to PADIs for a while if you have too many failures. e.g. 1 minute is far too short an interval for Telekom-based services in Germany, use 5 minutes instead).

The script

# Cycles through different PPPoE credentials until one set works
# Which PPPoE interface?
:local pppoeInterface "pppoe-myvdsl"
# List of credentials to try in sequence. You need at least two sets here for the script to be useful.
# For safety, the current credentials MUST be in this list (probably first) or it won't start cycling when the interface goes down.
# Careful about the semicolons, due to a "feature" of RouterOS parser, entire lines will be silently discarded if you have too many semicolons.
:local pppoeCredentials {
  { user="user1@myoldisp"; password="pass1" };
  { user="user2@mynewisp"; password="pass2" };
  { user="user3"; password="pass3" };
# Find index of the credentials currently in use
# This is done first, so we can raise an error if they're missing (even if the interface is presently up)
:local curUser [ /interface pppoe-client get [ find name=$pppoeInterface ] user ]
:local curPassword [ /interface pppoe-client get [ find name=$pppoeInterface ] password ]
:local curCredentialsIndex -1
:for i from=0 to=([:len $pppoeCredentials]-1) do={
  :local thisCredentials [ :pick $pppoeCredentials $i ]
  :log debug ("Available credentials for $pppoeInterface: user " . ($thisCredentials->"user") . " password " . ($thisCredentials->"password"))
  :if [ (($thisCredentials->"user") = $curUser) && (($thisCredentials->"password") = $curPassword) ] do={
    :set curCredentialsIndex $i
:if [ ($curCredentialsIndex < 0) ] do={
  :log error "Current credentials for $pppoeInterface are not in the list, aborting!"
  :error 1
# if interface is up, we don't need to do anything
:if [ /interface get [ find name=$pppoeInterface ] running ] do={
  :log debug "Interface $pppoeInterface is up, nothing to do"
  :return 0
# if interface is disabled, no point doing anything
:if [ /interface get [ find name=$pppoeInterface ] disabled ] do={
  :log debug "Interface $pppoeInterface is disabled, nothing to do"
  :return 0
# Get the new credentials
:local newCredentialsIndex ($curCredentialsIndex + 1)
if [ ($newCredentialsIndex >= [:len $pppoeCredentials]) ] do={
  # Wrap back to the first
  :log debug "Reached end of credentials list, looping back to start"
  :set newCredentialsIndex 0
:local newCredentials [ :pick $pppoeCredentials $newCredentialsIndex ]
# Set the new credentials
:local newUser ($newCredentials->"user")
:log info "Switching PPPoE credentials on interface $pppoeInterface to user $newUser"
/interface pppoe-client set [ find name=$pppoeInterface ] user=($newCredentials->"user") password=($newCredentials->"password")

Check you have entered the credentials correctly

It is very easy to "lose" credentials from the list due to the "special" way RouterOS silently drops entire arrays that have too many semicolons in them.

So it's worth running it with debug on once to make sure that all the credentials you expect to see are in fact there. /system logging add topics=script will cause the list to be output to the log. Turn it off again with /system logging remove [ find topics=script ]

Why it won't work if you don't have the current credentials in there:

It will not run at all if it can't find the current credentials in the array. This results in a loud ERROR output to the log even if the interface is currently up.

This is a deliberate design decision to prevent the following situation: Your migration is happening next week, and you have started this script in advance. But this week your DSL goes down for a completely unrelated reason. The script dumbly starts cycling through the supplied credentials to try to get the interface to come back up. But you have fat-fingered the current credentials (or forgotten to add them to the script altogether). Now you will have no PPPoE session at all! To prevent this situation, it is REQUIRED to add the current credentials to the script.