Checking Recursive Route values

I’ve been using the following script snippet to get the status of Recursive Routes in a MultiWAN setting. Works well under ROS v6, but is no longer compatible with ROS v7. I use the values to determine which recursive route is currently active/ default, then make some changes elsewhere in the router.

So, maybe there is a better way to go about this? Anyone know the new way to obtain this information? Under ROS v7, how to know which Recursive Route is the default route?


# route variables
:local  sHost1     "8.8.8.8";
:local  sHost2     "9.9.9.9";

# Get the current Gateway address values
:local sGWCur1 [ip route get [find active=yes 0.0.0.0/0 in dst-address !routing-mark] gateway]
:local sGWCur2 ([ip route check $sHost1 once as-value]->"nexthop")
:local sGWCur3 ([ip route check $sHost2 once as-value]->"nexthop")

personally i always have used recursive routing for this

Understood, but which recursive route is active? That is the question. I need to store the value.

How to tell which recurse route is active?

They removed “/ip route check” in V7. I presume it used the route cache in V6, and since there is no route cache in V7, they just removed it instead of fixing it. Quite annoying in a “multi-WAN” situation & especially so with recursive routes. See http://forum.mikrotik.com/t/ip-route-check-command-disappeared/141645/1

@Ammo,

Thank you so much. So, I think I’ve found a workaround for myself. I will post all when done. For now, could you help me with the scripting portion? Below is my sample script.

Questions:
I don’t know how to select things in ROS. The sample script logic below shows getting the ip route data for the ISP1 recursive route (by matching on comment name and active status). This gives me a table of data. I would then like to only select out of that the GATEWAY column if possible so I have a simple string (not an array). What is cool is that if this command returns a blank string, I know I’m not on ISP1 recursive route. Again, this is just a sample, I’ll show more logic once I get this working.

Could you make the following work?


# Name: onGatewayChange
# Add to Scheduler and set to run every 10 seconds
# http://forum.mikrotik.com/t/checking-recursive-route-values/167746/1

# variables to hold route checks and other items
:local  sHost1      "8.8.8.8";
:local  sHost2      "9.9.9.9";
:local  sHost1Str   "ISP1_GW";
:local  sHost2Str   "ISP2_GW";

# global variables for monitoring via System/Scripts GUI
:global sGateway1;
:global sGateway2;

# Get current Gateway value
:local sGateway1Current [ip route print where comment="$sHost1Str" and active and routing-table="main"]

# debug
:log info "Start";
:log info "$sGateway1Current";

# 1: Current State of the environment
:if ( ($sGateway1Current = $sHost1) || ($sGateway1Current = $sHost2) ) do={

        :log info "$sGateway1Current";
        :log info "$sHost1Str is live";

} else={

        :if ( $sGateway1Current = "" ) do={

        :log info "$sHost1Str is blank which means route 2 is most likely active";
        # do other stuff
	}
}


# debug
:log info "Finished";

I’m not sure your logic is entirely right however & I’m not sure my code is entirely right even if it was – I don’t have any RR setup to test… But that’s what I spotted at first glance.

Name: onGatewayChange

Add to Scheduler and set to run every 10 seconds

> http://forum.mikrotik.com/t/checking-recursive-route-values/167746/1

variables to hold route checks and other items

:local sHost1 “8.8.8.8”;
:local sHost2 “9.9.9.9”;
:local sHost1Str “ISP1_GW”;
:local sHost2Str “ISP2_GW”;

global variables for monitoring via System/Scripts GUI

:global sGateway1;
:global sGateway2;

Get current Gateway value

:local sGateway1Current > (> [ip route print > as-value > where comment=“$sHost1Str” and active and routing-table=“main”]> )->0->“gateway”)

debug

:log info “Start”;
:log info “$sGateway1Current”;

1: Current State of the environment

:if ( ($sGateway1Current = $sHost1) || ($sGateway1Current = $sHost2) ) do={

:log info “$sGateway1Current”;
:log info “$sHost1Str is live”;

} else={

:if > ( [:typeof $sGateway1Current] = nothing ) > do={

:log info “$sHost1Str is blank which means route 2 is most likely active”;

do other stuff

}
}

\

debug

:log info “Finished”;

Perhaps answering the first part of your question be better… Teach a man to fish…

Maybe this helps… Basically you need a “->” (accessor operator) always surrounded by ( ) to group what array is actually accessing. Now the accessor itself can be a number, or a string - depending the actual array structure. The array structure can be either an “ordered list” OR “map”… The list structure has an order and it does not change and starts at 0. While in a map structured array, there are “attributes” and “values”. Analogy with config is “find” creates an ordered list array, while a “get” (without an attribute) returns an array map.

Some example may help you understand the array philosophy…


Simple Example of Ordered List

{
# ordered listed are declared using {} with ; inbetween & can be any type
:local orderedList {"cat";"dog";3;8.8.8.8}

:put ($orderedList->0)
#cat
:put ($orderedList->1) 
#dog
:put ($orderedList->2) 
#3
:put [:typeof ($orderedList->3)] 
#ip
:put [:typeof ($orderedList->4)] 
#nothing -since there isn't one, it's type is actually a [i]nothing[/i] type, NOT string or anything...
}

Example - Getting first item in an array returned from a command
print normally outputs to console, and does not return an array by default, so you’ll need an “as-value” to cause it return an array. And, with the “as-value” print returns an ordered list, you use a number to access it. So ->0 is the first element in the array…

:put ([/ip route print as-value where active]->0)

But what if it’s empty list…
Since it’s possible there may not be any active routes… you’d like want to store and check that…

{
:local allRoutes [/ip route print as-value where active]
# one way is using :len... to know if you should put the first one...
:if ([:len $allRoutes] > 0) do={:put ($allRoutes->0)}
# or by it's type being NOT a nothing type...
:if ([:typeof ($allRoutes->0)] != "nothing") do={:put ($allRoutes->0))
}

Simple Example of Map Array e.g. “key=value” ones
These work by using = to define the key/attribute-name and value, with a semi-colon ; separating the attributes. The part before the = is what you use to access it, with the part after = what’s returned… You have to use quotes around the attribute (or key). And, you can NOT use an index like ->0 to access the “first” attributes – a “map” (aka key-values) is actually stored alphabetically and this style of array does not have an “index”.

{
:local vlanspecs {vid=10;type="access"}
:put ($vlanspecs->"vid")
:put ($vlanspecs->"type")
:put [:typeof ($vlanspecs->"somethingnotthere")]
}

List of Maps - or… Looping through the routing table
To make things fun… arrays can be “nested” inside – so an array list can contain an array map as one of its elements. One rule is at any “dimension” it’s always either a list or map – it cannot be both at same level. But…an array list can contain an array map as one of the values in the list & an array map attribute can store an array list (or even another array map) as it’s value.

An ordered list is what “print as-value” returns. But inside the ordered list is a map of each of the attributes…

A RouterOS :foreach can help enumerate all the values an “list of maps” (list):

{
    :foreach foundItem in=[/ip/route/print as-value] do={
        # print all attributes for current route in loop
        :put $foundItem
        # print just the comment attribute - note the quotes since each route is a map of the attributes
        :put ($foundItem->"comment")

        # since the $foundItem is an array map...
        # we can loop  and get BOTH attribute and values for the current route
        :foreach routeAttr,attrValue in=$foundItem do={
            :put "$routeAttr is set to $attrValue"
        }
    }
}

LMK if you have more questions.

Could you show me a generic version of this cmd that works on the terminal? I’m struggling.

/ip route print detail as-value where comment=“Example” and active and routing-table=“main”])->0->“gateway”

Almost…
I guess that was a long way to say:

:put ([/ip route print detail as-value where comment="Example" and active and routing-table="main"]->0->"gateway")

Always need (parentheses) if you use the → operator…

And, it won’t print anything if you use the as-value, so you need the :put at the command line to see what the value is. You can store it as :local or :global without the :put instead.

:global gw0 ([/ip route print detail as-value where comment="Example" and active and routing-table="main"]->0->"gateway")
:put $gw0

or broken up…

:global rtall ([/ip route print detail as-value where active and routing-table=main and comment~".*"])
# show the length (e.g. number of routes that match the "where")
:put [:len $rtall]
# first match...
:put ($rtall->0->"gateway")
:put ($rtall->0->"dst-address")
# 2nd match
:put ($rtall->1->"gateway")
:put ($rtall->1->"dst-address")

@Amm0,

Thank you so much! This really helps. It works now. I will continue to build out a script that replaces what I was doing. Example for others:


# Name: onGatewayChange Tested with ROS v7.9.2
# Add to Scheduler and set to run every X seconds
# http://forum.mikrotik.com/t/checking-recursive-route-values/167746/1

# variables to hold route checks and other customized items
:local  Route1	"8.8.8.8";
:local  Route2	"9.9.9.9";
:local  Route3	"10.10.10.10";
:local  ISP1	"ISP1";
:local  ISP2	"ISP2";

#####################################################################
# BEGIN SCRIPT
#####################################################################

# System variables
:global GWORG;
:global GWCUR;
:local  bFlag true;

# Get the current Gateway address
:set GWCUR ([/ip route print detail as-value where active and routing-table="main"]->0->"gateway")

# 1: Are things the same? Then nothing to do.
:if ( $GWCUR = $GWORG ) do={

	# Nothing to do. Environment has not changed.
	#:log info "Nothing to do";
	:set bFlag false;

} else={

		:log info "*** onGatewayChange ***";

		# something has changed. Are we on ISP1?
		:if ( ($GWCUR = $Route1) || ($GWCUR = $Route2) ) do={

			# Okay, still on ISP1, did we also come from ISP1?
			:if ( ($GWORG = $Route1) || ($GWORG = $Route2) ) do={

				# This change meant we are still on ISP1. No need to update.
				:log info "Still on ISP1. Route was $GWORG now $GWCUR";
				:set bFlag false;
			}

			# Still on ISP1, did we come from ISP2?
			:if ( $GWORG = $Route3 ) do={

				# This change meant we are going back to ISP1. Signal an update.
				:log info "Switching back to ISP1. Route was $GWORG now $GWCUR";
				:set bFlag true;
			}
		}

		# something has changed. Are we on ISP2?
		:if ( $GWCUR = $Route3 ) do={

			# Did we come from ISP1?
			:if ( ($GWORG = $Route1) || ($GWORG = $Route2) ) do={

				# Failover mode to ISP2
				:log info "Failover mode to ISP2. Route was $GWORG now $GWCUR";
				:set bFlag true;
			}
		}

}


# Do failover
:if ($bFlag = true) do={

		# log changes
		:log info "Processing event";

		# React to which ISP
		:if ( ($GWCUR = $Route1) || ($GWCUR = $Route2) ) do={
			:log info "Do Something now that you're on ISP1."
		} else={
			:log info "Do Something now that you're on ISP2."
		}
}

# Udpate global variable
:set $GWORG $GWCUR;